commit 6142c2a5e5303f9bdf17ab4ae3f7687438b106a5 Author: yasir Date: Thu Dec 1 22:27:03 2022 +0700 Rilis Pertama diff --git a/.github/workflows/notify.yml b/.github/workflows/notify.yml new file mode 100644 index 00000000..cea9f59a --- /dev/null +++ b/.github/workflows/notify.yml @@ -0,0 +1,26 @@ +name: Notify on Telegram + +on: + fork: + push: + release: + issue_comment: + types: created + watch: + types: started + pull_request_review_comment: + types: created + pull_request: + types: [opened, closed, reopened] + issues: + types: [opened, pinned, closed, reopened] +jobs: + notify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Notify the commit on Telegram. + uses: EverythingSuckz/github-telegram-notify@main + with: + bot_token: '${{ secrets.BOT_TOKEN }}' + chat_id: '${{ secrets.CHAT_ID }}' diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 00000000..4fc59685 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,32 @@ +name: PyLint + +on: [pull_request, workflow_dispatch] + +jobs: + Pylint_Fix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install Python lint libraries + run: | + pip install autoflake black + - name: Remove unused imports and variables + run: | + autoflake --in-place --recursive --exclude "__main__.py" --remove-all-unused-imports --remove-unused-variables --ignore-init-module-imports . + - name: lint with black + run: | + black --exclude "__main__.py" --line-length 250 --target-version py310 . + # commit changes + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'reformating: code' + commit_options: '--no-verify' + repository: . + commit_user_name: yasirarism + commit_user_email: mail@yasir.eu.org + commit_author: yasirarism diff --git a/Calistoga-Regular.ttf b/Calistoga-Regular.ttf new file mode 100644 index 00000000..6ba77fb3 Binary files /dev/null and b/Calistoga-Regular.ttf differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c02df7e3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +# * @author Yasir Aris M +# * @date 2022-12-01 09:12:27 +# * @lastModified 2022-12-01 09:27:31 +# * @projectName MissKatyPyro +# * Copyright ©YasirPedia All rights reserved + +# Base Docker +FROM yasirarism/misskaty-docker:latest + +COPY . . +# Set CMD Bot +CMD ["python3", "-m", "misskaty"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..3943ec99 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +

+ MissKaty Bot based on Pyrogram +

+ +## NOTES +This repo has many bugs and i dont have time to fix it. If you can help me, please open pull request (PR). + +## Demo +You can check demo this repo in this bot [MissKatyPyro](https://t.me/MissKatyRoBot) + +## Features +I'm forget about it, try asking doraemon maybe know.. :) + +## Variables + +### Required Variables +* `BOT_TOKEN`: Create a bot using [@BotFather](https://telegram.dog/BotFather), and get the Telegram API token. +* `API_ID`: Get this value from [telegram.org](https://my.telegram.org/apps) +* `API_HASH`: Get this value from [telegram.org](https://my.telegram.org/apps) +* `ADMINS`: Username or ID of Admin. Separate multiple Admins by space +* `DATABASE_URI`: [mongoDB](https://www.mongodb.com) URI. Get this value from [mongoDB](https://www.mongodb.com). For more help watch this [video](https://youtu.be/1G1XwEOnxxo) +* `DATABASE_NAME`: Name of the database in [mongoDB](https://www.mongodb.com). For more help watch this [video](https://youtu.be/1G1XwEOnxxo) +* `LOG_CHANNEL` : A channel to log the activities of bot. Make sure bot is an admin in the channel. +### Optional Variables +Check by yourself for optional vars. + +## Deploy (Must Use Docker to Run This Bot) + +- Start Docker daemon (Skip if already running): +``` +sudo dockerd +``` +- Build Docker image: +``` +sudo docker build . -t misskaty +``` +- Run the image: +``` +sudo docker run misskaty +``` +- To stop the image: +``` +sudo docker ps +``` +``` +sudo docker stop id +``` + +---- + + +## Thanks to + - Thanks To Allah Swt. + - Thanks To Dan For His Awesome [Library](https://github.com/pyrogram/pyrogram). + - Thanks To [The Hamker Cat](https://github.com/TheHamkerCat) For Some Code. + - Thanks To [Team Yukki](https://github.com/TeamYukki) For Some Code. + - Thanks To [Wrench](https://github.com/EverythingSuckz) For Some Code. + - And All People Who Help Me In My Life... + If your code used in this repo and want to give credit please open issue.. + +## Disclaimer +[![GNU Affero General Public License 2.0](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html#header) +Licensed under [GNU AGPL 2.0.](https://github.com/yasirarism/MissKatyPyro/blob/master/LICENSE) +Selling The Codes To Other People For Money Is *Strictly Prohibited*. God always sees you. diff --git a/config.env.sample b/config.env.sample new file mode 100644 index 00000000..fac7aa6f --- /dev/null +++ b/config.env.sample @@ -0,0 +1,7 @@ +API_HASH= +DATABASE_NAME= +API_ID= +SUPPORT_CHAT=YasirPediaChannel +DATABASE_URI=mongodb+srv:// +BOT_TOKEN= +LOG_CHANNEL=- diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 00000000..94a04c6d --- /dev/null +++ b/database/__init__.py @@ -0,0 +1,12 @@ +""" + * @author yasir + * @date 2022-09-06 10:12:09 + * @lastModified 2022-12-01 09:34:27 + * @projectName MissKatyPyro + * Copyright @YasirPedia All rights reserved +""" +from motor.motor_asyncio import AsyncIOMotorClient as MongoClient +from misskaty.vars import DATABASE_URI + +mongo = MongoClient(DATABASE_URI) +dbname = mongo.MissKatyDB diff --git a/database/afk_db.py b/database/afk_db.py new file mode 100644 index 00000000..73cc890e --- /dev/null +++ b/database/afk_db.py @@ -0,0 +1,31 @@ +from database import dbname + +usersdb = dbname.users + + +async def is_afk(user_id: int) -> bool: + user = await usersdb.find_one({"user_id": user_id}) + return (True, user["reason"]) if user else (False, {}) + + +async def add_afk(user_id: int, mode): + await usersdb.update_one({"user_id": user_id}, {"$set": { + "reason": mode + }}, + upsert=True) + + +async def remove_afk(user_id: int): + user = await usersdb.find_one({"user_id": user_id}) + if user: + return await usersdb.delete_one({"user_id": user_id}) + + +async def get_afk_users() -> list: + users = usersdb.find({"user_id": {"$gt": 0}}) + if not users: + return [] + users_list = [] + for user in await users.to_list(length=1000000000): + users_list.append(user) + return users_list diff --git a/database/karma_db.py b/database/karma_db.py new file mode 100644 index 00000000..20b71c65 --- /dev/null +++ b/database/karma_db.py @@ -0,0 +1,66 @@ +from typing import Dict, Union +from misskaty.helper.functions import int_to_alpha +from database import dbname + +karmadb = dbname.karma + + +async def get_karmas_count() -> dict: + chats_count = 0 + karmas_count = 0 + async for chat in karmadb.find({"chat_id": {"$lt": 0}}): + for i in chat["karma"]: + karma_ = chat["karma"][i]["karma"] + if karma_ > 0: + karmas_count += karma_ + chats_count += 1 + return {"chats_count": chats_count, "karmas_count": karmas_count} + + +async def user_global_karma(user_id) -> int: + total_karma = 0 + async for chat in karmadb.find({"chat_id": {"$lt": 0}}): + karma = await get_karma(chat["chat_id"], await int_to_alpha(user_id)) + if karma and (int(karma["karma"]) > 0): + total_karma += int(karma["karma"]) + return total_karma + + +async def get_karmas(chat_id: int) -> Dict[str, int]: + karma = await karmadb.find_one({"chat_id": chat_id}) + return karma["karma"] if karma else {} + + +async def get_karma(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + karmas = await get_karmas(chat_id) + if name in karmas: + return karmas[name] + + +async def update_karma(chat_id: int, name: str, karma: dict): + name = name.lower().strip() + karmas = await get_karmas(chat_id) + karmas[name] = karma + await karmadb.update_one( + {"chat_id": chat_id}, {"$set": {"karma": karmas}}, upsert=True + ) + + +async def is_karma_on(chat_id: int) -> bool: + chat = await karmadb.find_one({"chat_id_toggle": chat_id}) + return bool(chat) + + +async def karma_on(chat_id: int): + is_karma = await is_karma_on(chat_id) + if is_karma: + return + return await karmadb.delete_one({"chat_id_toggle": chat_id}) + + +async def karma_off(chat_id: int): + is_karma = await is_karma_on(chat_id) + if not is_karma: + return + return await karmadb.insert_one({"chat_id_toggle": chat_id}) diff --git a/database/users_chats_db.py b/database/users_chats_db.py new file mode 100644 index 00000000..03474408 --- /dev/null +++ b/database/users_chats_db.py @@ -0,0 +1,105 @@ +import motor.motor_asyncio +from misskaty.vars import DATABASE_NAME, DATABASE_URI + + +class Database: + def __init__(self, uri, database_name): + self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) + self.db = self._client[database_name] + self.col = self.db.users + self.grp = self.db.groups + + def new_user(self, id, name): + return dict( + id=id, + name=name, + ban_status=dict( + is_banned=False, + ban_reason="", + ), + ) + + def new_group(self, id, title): + return dict( + id=id, + title=title, + chat_status=dict( + is_disabled=False, + reason="", + ), + ) + + async def add_user(self, id, name): + user = self.new_user(id, name) + await self.col.insert_one(user) + + async def is_user_exist(self, id): + user = await self.col.find_one({"id": int(id)}) + return bool(user) + + async def total_users_count(self): + return await self.col.count_documents({}) + + async def remove_ban(self, id): + ban_status = dict(is_banned=False, ban_reason="") + await self.col.update_one({"id": id}, {"$set": {"ban_status": ban_status}}) + + async def ban_user(self, user_id, ban_reason="No Reason"): + ban_status = dict(is_banned=True, ban_reason=ban_reason) + await self.col.update_one({"id": user_id}, {"$set": {"ban_status": ban_status}}) + + async def get_ban_status(self, id): + default = dict(is_banned=False, ban_reason="") + user = await self.col.find_one({"id": int(id)}) + return user.get("ban_status", default) if user else default + + async def get_all_users(self): + return self.col.find({}) + + async def delete_user(self, user_id): + await self.col.delete_many({"id": int(user_id)}) + + async def get_banned(self): + users = self.col.find({"ban_status.is_banned": True}) + chats = self.grp.find({"chat_status.is_disabled": True}) + b_chats = [chat["id"] async for chat in chats] + b_users = [user["id"] async for user in users] + return b_users, b_chats + + async def add_chat(self, chat, title): + chat = self.new_group(chat, title) + await self.grp.insert_one(chat) + + async def get_chat(self, chat): + chat = await self.grp.find_one({"id": int(chat)}) + return chat.get("chat_status") if chat else False + + async def re_enable_chat(self, id): + chat_status = dict( + is_disabled=False, + reason="", + ) + await self.grp.update_one( + {"id": int(id)}, {"$set": {"chat_status": chat_status}} + ) + + async def disable_chat(self, chat, reason="No Reason"): + chat_status = dict( + is_disabled=True, + reason=reason, + ) + await self.grp.update_one( + {"id": int(chat)}, {"$set": {"chat_status": chat_status}} + ) + + async def total_chat_count(self): + return await self.grp.count_documents({}) + + async def get_all_chats(self): + return self.grp.find({}) + + async def get_db_size(self): + return (await self.db.command("dbstats"))["dataSize"] + + +db = Database(DATABASE_URI, DATABASE_NAME) diff --git a/database/warn_db.py b/database/warn_db.py new file mode 100644 index 00000000..953f7779 --- /dev/null +++ b/database/warn_db.py @@ -0,0 +1,53 @@ +from typing import Dict, Union +from database import dbname + +warnsdb = dbname.warn + + +async def get_warns_count() -> dict: + chats_count = 0 + warns_count = 0 + async for chat in warnsdb.find({"chat_id": {"$lt": 0}}): + for user in chat["warns"]: + warns_count += chat["warns"][user]["warns"] + chats_count += 1 + return {"chats_count": chats_count, "warns_count": warns_count} + + +async def get_warns(chat_id: int) -> Dict[str, int]: + warns = await warnsdb.find_one({"chat_id": chat_id}) + return warns["warns"] if warns else {} + + +async def get_warn(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + warns = await get_warns(chat_id) + if name in warns: + return warns[name] + + +async def add_warn(chat_id: int, name: str, warn: dict): + name = name.lower().strip() + warns = await get_warns(chat_id) + warns[name] = warn + + await warnsdb.update_one({"chat_id": chat_id}, {"$set": { + "warns": warns + }}, + upsert=True) + + +async def remove_warns(chat_id: int, name: str) -> bool: + warnsd = await get_warns(chat_id) + name = name.lower().strip() + if name in warnsd: + del warnsd[name] + await warnsdb.update_one( + {"chat_id": chat_id}, + {"$set": { + "warns": warnsd + }}, + upsert=True, + ) + return True + return False diff --git a/img/bg.png b/img/bg.png new file mode 100644 index 00000000..39bec0ef Binary files /dev/null and b/img/bg.png differ diff --git a/img/profilepic.png b/img/profilepic.png new file mode 100644 index 00000000..5f6674f6 Binary files /dev/null and b/img/profilepic.png differ diff --git a/logging.conf b/logging.conf new file mode 100644 index 00000000..c523b779 --- /dev/null +++ b/logging.conf @@ -0,0 +1,32 @@ +[loggers] +keys=root + +[handlers] +keys=consoleHandler,fileHandler + +[formatters] +keys=consoleFormatter,fileFormatter + +[logger_root] +level=DEBUG +handlers=consoleHandler,fileHandler + +[handler_consoleHandler] +class=StreamHandler +level=INFO +formatter=consoleFormatter +args=(sys.stdout,) + +[handler_fileHandler] +class=FileHandler +level=ERROR +formatter=fileFormatter +args=('MissKatyLogs.txt','w',) + +[formatter_consoleFormatter] +format=%(asctime)s - %(lineno)d - %(name)s - %(module)s - %(levelname)s - %(message)s +datefmt=%I:%M:%S %p + +[formatter_fileFormatter] +format=[%(asctime)s:%(name)s:%(lineno)d:%(levelname)s] %(message)s +datefmt=%m/%d/%Y %I:%M:%S %p diff --git a/misskaty/__init__.py b/misskaty/__init__.py new file mode 100644 index 00000000..4d51adbc --- /dev/null +++ b/misskaty/__init__.py @@ -0,0 +1,31 @@ +import logging +import time +import logging.config + +# Get logging +logging.config.fileConfig("logging.conf") +logging.getLogger().setLevel(logging.INFO) +logging.getLogger("pyrogram").setLevel(logging.ERROR) +from pyrogram import Client +from misskaty.vars import API_ID, API_HASH, BOT_TOKEN, USER_SESSION + +MOD_LOAD = [] +MOD_NOLOAD = [] +HELPABLE = {} + +botStartTime = time.time() + +# Pyrogram Bot Client +app = Client( + name="MissKatyBot", + api_id=API_ID, + api_hash=API_HASH, + bot_token=BOT_TOKEN, + sleep_threshold=5, +) + +# Pyrogram UserBot Client +user = Client( + name="YasirUBot", + session_string=USER_SESSION, +) diff --git a/misskaty/__main__.py b/misskaty/__main__.py new file mode 100644 index 00000000..32b59c1b --- /dev/null +++ b/misskaty/__main__.py @@ -0,0 +1,342 @@ +""" + * @author yasir + * @date 2022-12-01 09:12:27 + * @lastModified 2022-12-01 09:33:16 + * @projectName MissKatyPyro + * Copyright @YasirPedia All rights reserved + """ +import asyncio, importlib, re, logging +from misskaty import app, user, HELPABLE +from misskaty.plugins import ALL_MODULES +from misskaty.helper import paginate_modules +from misskaty.helper.tools import bot_sys_stats +from database.users_chats_db import db +from misskaty.vars import LOG_CHANNEL +from utils import temp +from logging import info as log_info +from pyrogram.raw.all import layer +from pyrogram import idle, __version__, filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup + +loop = asyncio.get_event_loop() + + +# Run Bot +async def start_bot(): + global HELPABLE + + for module in ALL_MODULES: + imported_module = importlib.import_module(f"misskaty.plugins.{module}") + if hasattr(imported_module, "__MODULE__") and imported_module.__MODULE__: + imported_module.__MODULE__ = imported_module.__MODULE__ + if hasattr(imported_module, "__HELP__") and imported_module.__HELP__: + HELPABLE[imported_module.__MODULE__.lower()] = imported_module + bot_modules = "" + j = 1 + for i in ALL_MODULES: + if j == 4: + bot_modules += "|{:<15}|\n".format(i) + j = 0 + else: + bot_modules += "|{:<15}".format(i) + j += 1 + await app.start() + await user.start() + me = await app.get_me() + ubot = await user.get_me() + log_info("+===============================================================+") + log_info("| MissKatyPyro |") + log_info("+===============+===============+===============+===============+") + log_info(bot_modules) + log_info("+===============+===============+===============+===============+") + log_info(f"[INFO]: BOT STARTED AS @{me.username}!") + + try: + log_info("[INFO]: SENDING ONLINE STATUS") + await app.send_message( + 617426792, + f"USERBOT AND BOT STARTED with Pyrogram v{__version__}..\nUserBot: {ubot.first_name}\nBot: {me.first_name}\n\nwith Pyrogram v{__version__} (Layer {layer}) started on @{me.username}.", + ) + except Exception: + pass + + await idle() + await app.stop() + await user.stop() + print("[INFO]: Bye!") + + +home_keyboard_pm = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="Commands ❓", callback_data="bot_commands"), + InlineKeyboardButton( + text="Github Repo 🛠", + url="https://github.com/yasirarism/MissKatyPyro", + ), + ], + [ + InlineKeyboardButton( + text="System Stats 🖥", + callback_data="stats_callback", + ), + InlineKeyboardButton(text="Dev 👨", url="https://t.me/YasirArisM"), + ], + [ + InlineKeyboardButton( + text="Add Me To Your Group 🎉", + url="http://t.me/MissKatyRoBot?startgroup=new", + ) + ], + ] +) + +home_text_pm = f"Hey there! My name is MissKatyRoBot. I have many useful features for you, feel free to add me to your group.\n\nIf you want give coffee to my owner you can send /donate command for more info." + +keyboard = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="Help ❓", url="t.me/MissKatyRoBot?start=help"), + InlineKeyboardButton( + text="Github Repo �", + url="https://github.com/yasirarism/MissKatyPyro", + ), + ], + [ + InlineKeyboardButton( + text="System Stats 💻", + callback_data="stats_callback", + ), + InlineKeyboardButton(text="Dev 👨", url="https://t.me/YasirArisM"), + ], + ] +) + + +@app.on_message(filters.command("start")) +async def start(_, message): + if message.chat.type.value != "private": + if not await db.get_chat(message.chat.id): + total = await app.get_chat_members_count(message.chat.id) + await app.send_message( + LOG_CHANNEL, + f"#NewGroup\nGroup = {message.chat.title}({message.chat.id})\nMembers Count = {total}\nAdded by - Unknown", + ) + + await db.add_chat(message.chat.id, message.chat.title) + nama = ( + message.from_user.mention + if message.from_user + else message.sender_chat.title + ) + return await message.reply_photo( + photo="https://telegra.ph/file/90e9a448bc2f8b055b762.jpg", + caption=f"Hi {nama}, Pm Me For More Info About Me.", + reply_markup=keyboard, + ) + if not await db.is_user_exist(message.from_user.id): + await db.add_user(message.from_user.id, message.from_user.first_name) + await app.send_message( + LOG_CHANNEL, + f"#NewUser\nID - {message.from_user.id}\nName - {message.from_user.mention}", + ) + + if len(message.text.split()) > 1: + name = (message.text.split(None, 1)[1]).lower() + if "_" in name: + module = name.split("_", 1)[1] + text = ( + f"Here is the help for **{HELPABLE[module].__MODULE__}**:\n" + + HELPABLE[module].__HELP__ + ) + await message.reply(text, disable_web_page_preview=True) + elif name == "help": + text, keyb = await help_parser(message.from_user.first_name) + await message.reply( + text, + reply_markup=keyb, + ) + else: + await message.reply_photo( + photo="https://telegra.ph/file/90e9a448bc2f8b055b762.jpg", + caption=home_text_pm, + reply_markup=home_keyboard_pm, + ) + + +@app.on_callback_query(filters.regex("bot_commands")) +async def commands_callbacc(_, CallbackQuery): + text, keyboard = await help_parser(CallbackQuery.from_user.mention) + await app.send_message( + CallbackQuery.message.chat.id, + text=text, + reply_markup=keyboard, + ) + + await CallbackQuery.message.delete() + + +@app.on_callback_query(filters.regex("stats_callback")) +async def stats_callbacc(_, CallbackQuery): + text = await bot_sys_stats() + await app.answer_callback_query(CallbackQuery.id, text, show_alert=True) + + +@app.on_message(filters.command("help")) +async def help_command(_, message): + if message.chat.type.value != "private": + if not await db.get_chat(message.chat.id): + total = await app.get_chat_members_count(message.chat.id) + await app.send_message( + LOG_CHANNEL, + f"#NewGroup\nGroup = {message.chat.title}({message.chat.id})\nMembers Count = {total}\nAdded by - Unknown", + ) + + await db.add_chat(message.chat.id, message.chat.title) + if len(message.command) >= 2: + name = (message.text.split(None, 1)[1]).replace(" ", "_").lower() + if str(name) in HELPABLE: + key = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Click here", + url=f"t.me/MissKatyRoBot?start=help_{name}", + ) + ], + ] + ) + await message.reply( + f"Click on the below button to get help about {name}", + reply_markup=key, + ) + else: + await message.reply("PM Me For More Details.", reply_markup=keyboard) + else: + await message.reply("Pm Me For More Details.", reply_markup=keyboard) + else: + if not await db.is_user_exist(message.from_user.id): + await db.add_user(message.from_user.id, message.from_user.first_name) + await app.send_message( + LOG_CHANNEL, + f"#NewUser\nID - {message.from_user.id}\nName - {message.from_user.mention}", + ) + + if len(message.command) >= 2: + name = (message.text.split(None, 1)[1]).replace(" ", "_").lower() + if str(name) in HELPABLE: + text = ( + f"Here is the help for **{HELPABLE[name].__MODULE__}**:\n" + + HELPABLE[name].__HELP__ + ) + await message.reply(text, disable_web_page_preview=True) + else: + text, help_keyboard = await help_parser(message.from_user.first_name) + await message.reply( + text, + reply_markup=help_keyboard, + disable_web_page_preview=True, + ) + else: + text, help_keyboard = await help_parser(message.from_user.first_name) + await message.reply( + text, reply_markup=help_keyboard, disable_web_page_preview=True + ) + return + + +async def help_parser(name, keyboard=None): + if not keyboard: + keyboard = InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")) + return ( + """Hello {first_name}, My name is {bot_name}. +I'm a bot with some useful features. +You can choose an option below, by clicking a button. + +If you want give coffee to my owner you can send /donate command for more info. +""".format( + first_name=name, + bot_name="MissKaty", + ), + keyboard, + ) + + +@app.on_callback_query(filters.regex(r"help_(.*?)")) +async def help_button(client, query): + home_match = re.match(r"help_home\((.+?)\)", query.data) + mod_match = re.match(r"help_module\((.+?)\)", query.data) + prev_match = re.match(r"help_prev\((.+?)\)", query.data) + next_match = re.match(r"help_next\((.+?)\)", query.data) + back_match = re.match(r"help_back", query.data) + create_match = re.match(r"help_create", query.data) + top_text = f""" +Hello {query.from_user.first_name}, My name is MissKaty. +I'm a bot with some usefule features. +You can choose an option below, by clicking a button below. + +General command are: + - /start: Start the bot + - /help: Give this message + """ + if mod_match: + module = mod_match[1].replace(" ", "_") + text = f"Here is the help for **{HELPABLE[module].__MODULE__}**:\n{HELPABLE[module].__HELP__}" + + await query.message.edit( + text=text, + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("back", callback_data="help_back")]] + ), + disable_web_page_preview=True, + ) + elif home_match: + await app.send_message( + query.from_user.id, + text=home_text_pm, + reply_markup=home_keyboard_pm, + ) + await query.message.delete() + elif prev_match: + curr_page = int(prev_match[1]) + await query.message.edit( + text=top_text, + reply_markup=InlineKeyboardMarkup( + paginate_modules(curr_page - 1, HELPABLE, "help") + ), + disable_web_page_preview=True, + ) + + elif next_match: + next_page = int(next_match[1]) + await query.message.edit( + text=top_text, + reply_markup=InlineKeyboardMarkup( + paginate_modules(next_page + 1, HELPABLE, "help") + ), + disable_web_page_preview=True, + ) + + elif back_match: + await query.message.edit( + text=top_text, + reply_markup=InlineKeyboardMarkup(paginate_modules(0, HELPABLE, "help")), + disable_web_page_preview=True, + ) + + elif create_match: + text, keyboard = await help_parser(query) + await query.message.edit( + text=text, + reply_markup=keyboard, + disable_web_page_preview=True, + ) + + return await client.answer_callback_query(query.id) + + +if __name__ == "__main__": + try: + loop.run_until_complete(start_bot()) + except KeyboardInterrupt: + logging.info("----------------------- Service Stopped -----------------------") diff --git a/misskaty/core/decorator/errors.py b/misskaty/core/decorator/errors.py new file mode 100644 index 00000000..bd7d6650 --- /dev/null +++ b/misskaty/core/decorator/errors.py @@ -0,0 +1,59 @@ +import traceback, asyncio +from functools import wraps +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden +from misskaty.vars import LOG_CHANNEL +from misskaty import app + + +def asyncify(func): + async def inner(*args, **kwargs): + loop = asyncio.get_running_loop() + func_out = await loop.run_in_executor(None, func, *args, **kwargs) + return func_out + + return inner + + +def split_limits(text): + if len(text) < 2048: + return [text] + + lines = text.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 2048: + small_msg += line + else: + result.append(small_msg) + small_msg = line + result.append(small_msg) + + return result + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await app.leave_chat(message.chat.id) + return + except Exception as err: + exc = traceback.format_exc() + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + message.from_user.id if message.from_user else 0, + message.chat.id if message.chat else 0, + message.text or message.caption, + exc, + ) + ) + + for x in error_feedback: + await app.send_message(LOG_CHANNEL, x) + await message.reply(x) + raise err + + return capture diff --git a/misskaty/core/decorator/permissions.py b/misskaty/core/decorator/permissions.py new file mode 100644 index 00000000..dcd5974f --- /dev/null +++ b/misskaty/core/decorator/permissions.py @@ -0,0 +1,109 @@ +from functools import wraps +from traceback import format_exc as err +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden +from pyrogram.types import Message +from pyrogram import enums +from misskaty import app +from misskaty.vars import SUDO +from time import time + + +async def member_permissions(chat_id: int, user_id: int): + perms = [] + try: + member = await app.get_chat_member(chat_id, user_id) + perijinan = member.privileges + except Exception: + return [] + if member.status != enums.ChatMemberStatus.MEMBER: + if perijinan.can_post_messages: + perms.append("can_post_messages") + if perijinan.can_edit_messages: + perms.append("can_edit_messages") + if perijinan.can_delete_messages: + perms.append("can_delete_messages") + if perijinan.can_restrict_members: + perms.append("can_restrict_members") + if perijinan.can_promote_members: + perms.append("can_promote_members") + if perijinan.can_change_info: + perms.append("can_change_info") + if perijinan.can_invite_users: + perms.append("can_invite_users") + if perijinan.can_pin_messages: + perms.append("can_pin_messages") + if perijinan.can_manage_video_chats: + perms.append("can_manage_video_chats") + return perms + + +admins_in_chat = {} + + +async def list_admins(chat_id: int): + global admins_in_chat + if chat_id in admins_in_chat: + interval = time() - admins_in_chat[chat_id]["last_updated_at"] + if interval < 3600: + return admins_in_chat[chat_id]["data"] + + admins_in_chat[chat_id] = { + "last_updated_at": time(), + "data": [member.user.id async for member in app.get_chat_members(chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS)], + } + return admins_in_chat[chat_id]["data"] + + +async def authorised(func, subFunc2, client, message, *args, **kwargs): + chatID = message.chat.id + try: + await func(client, message, *args, **kwargs) + except ChatWriteForbidden: + await app.leave_chat(chatID) + except Exception as e: + try: + await message.reply_text(str(e.MESSAGE)) + except AttributeError: + await message.reply_text(str(e)) + e = err() + print(e) + return subFunc2 + + +async def unauthorised(message: Message, permission, subFunc2): + chatID = message.chat.id + text = "You don't have the required permission to perform this action." + f"\n**Permission:** __{permission}__" + try: + await message.reply_text(text) + except ChatWriteForbidden: + await app.leave_chat(chatID) + return subFunc2 + + +def adminsOnly(permission): + def subFunc(func): + @wraps(func) + async def subFunc2(client, message: Message, *args, **kwargs): + chatID = message.chat.id + if not message.from_user: + # For anonymous admins + if message.sender_chat and message.sender_chat.id == message.chat.id: + return await authorised( + func, + subFunc2, + client, + message, + *args, + **kwargs, + ) + return await unauthorised(message, permission, subFunc2) + # For admins and sudo users + userID = message.from_user.id + permissions = await member_permissions(chatID, userID) + if userID not in SUDO and permission not in permissions: + return await unauthorised(message, permission, subFunc2) + return await authorised(func, subFunc2, client, message, *args, **kwargs) + + return subFunc2 + + return subFunc diff --git a/misskaty/core/keyboard.py b/misskaty/core/keyboard.py new file mode 100644 index 00000000..1736ff66 --- /dev/null +++ b/misskaty/core/keyboard.py @@ -0,0 +1,26 @@ +from pykeyboard import InlineKeyboard +from pyrogram.types import InlineKeyboardButton as Ikb + +from misskaty.helper.functions import get_urls_from_text as is_url + + +def keyboard(buttons_list, row_width: int = 2): + """ + Buttons builder, pass buttons in a list and it will + return pyrogram.types.IKB object + Ex: keyboard([["click here", "https://google.com"]]) + if theres, a url, it will make url button, else callback button + """ + buttons = InlineKeyboard(row_width=row_width) + data = [Ikb(text=str(i[0]), url=str(i[1])) if is_url(i[1]) else Ikb(text=str(i[0]), callback_data=str(i[1])) for i in buttons_list] + + buttons.add(*data) + return buttons + + +def ikb(data: dict, row_width: int = 2): + """ + Converts a dict to pyrogram buttons + Ex: dict_to_keyboard({"click here": "this is callback data"}) + """ + return keyboard(data.items(), row_width=row_width) diff --git a/misskaty/helper/__init__.py b/misskaty/helper/__init__.py new file mode 100644 index 00000000..e20f7f0c --- /dev/null +++ b/misskaty/helper/__init__.py @@ -0,0 +1 @@ +from .misc import paginate_modules diff --git a/misskaty/helper/ffmpeg_helper.py b/misskaty/helper/ffmpeg_helper.py new file mode 100644 index 00000000..b76c230f --- /dev/null +++ b/misskaty/helper/ffmpeg_helper.py @@ -0,0 +1,61 @@ +import asyncio +import os +import time +from pyrogram.types import InputMediaPhoto +from misskaty.plugins.dev import shell_exec +from pyrogram.errors import FloodWait + + +def hhmmss(seconds): + return time.strftime("%H:%M:%S", time.gmtime(seconds)) + + +async def take_ss(video_file): + out_put_file_name = f"genss{str(time.time())}.png" + cmd = f"ssmedia '{video_file}' -t -w 1340 -g 4x4 --ffmpeg-name mediaextract --quality 100 --end-delay-percent 20 --metadata-font-size 30 --timestamp-font-size 20 -o {out_put_file_name}" + await shell_exec(cmd) + return out_put_file_name if os.path.lexists(out_put_file_name) else None + + +async def ssgen_link(video, output_directory, ttl): + out_put_file_name = f"{output_directory}/{str(time.time())}.png" + cmd = [ + "mediaextract", + "-ss", + str(ttl), + "-i", + video, + "-vframes", + "1", + "-f", + "image2", + out_put_file_name, + ] + 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() + return out_put_file_name if os.path.isfile(out_put_file_name) else None + + +async def genss_link(msg, video_link, output_directory, min_duration, no_of_photos): + metadata = (await shell_exec(f"ffprobe -i {video_link} -show_entries format=duration -v quiet -of csv='p=0'"))[0] + duration = round(float(metadata)) + if duration > min_duration: + images = [] + ttl_step = duration // no_of_photos + current_ttl = ttl_step + for looper in range(no_of_photos): + ss_img = await ssgen_link(video_link, output_directory, current_ttl) + images.append(InputMediaPhoto(media=ss_img, caption=f"Screenshot at {hhmmss(current_ttl)}")) + try: + await msg.edit(f"📸 Take Screenshoot:\n{looper+1} of {no_of_photos} screenshot generated..") + except FloodWait as e: + await asyncio.sleep(e.value) + await msg.edit(f"📸 Take Screenshoot:\n{looper+1} of {no_of_photos} screenshot generated..") + current_ttl = current_ttl + ttl_step + await asyncio.sleep(2) + return images + else: + return None diff --git a/misskaty/helper/files.py b/misskaty/helper/files.py new file mode 100644 index 00000000..c5a91845 --- /dev/null +++ b/misskaty/helper/files.py @@ -0,0 +1,82 @@ +""" +MIT License +Copyright (c) 2021 TheHamkerCat +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import math +import os + +from PIL import Image +from pyrogram import Client, raw +from pyrogram.file_id import FileId + +STICKER_DIMENSIONS = (512, 512) + + +async def resize_file_to_sticker_size(file_path: str) -> str: + im = Image.open(file_path) + if (im.width, im.height) < STICKER_DIMENSIONS: + size1 = im.width + size2 = im.height + if im.width > im.height: + scale = STICKER_DIMENSIONS[0] / size1 + size1new = STICKER_DIMENSIONS[0] + size2new = size2 * scale + else: + scale = STICKER_DIMENSIONS[1] / size2 + size1new = size1 * scale + size2new = STICKER_DIMENSIONS[1] + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + im = im.resize(sizenew) + else: + im.thumbnail(STICKER_DIMENSIONS) + try: + os.remove(file_path) + return f"{file_path}.png" + finally: + im.save(file_path) + + +async def upload_document(client: Client, file_path: str, chat_id: int) -> raw.base.InputDocument: + media = await client.send( + raw.functions.messages.UploadMedia( + peer=await client.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + mime_type=client.guess_mime_type(file_path) or "application/zip", + file=await client.save_file(file_path), + attributes=[raw.types.DocumentAttributeFilename(file_name=os.path.basename(file_path))], + ), + ) + ) + return raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference, + ) + + +async def get_document_from_file_id( + file_id: str, +) -> raw.base.InputDocument: + decoded = FileId.decode(file_id) + return raw.types.InputDocument( + id=decoded.media_id, + access_hash=decoded.access_hash, + file_reference=decoded.file_reference, + ) diff --git a/misskaty/helper/functions.py b/misskaty/helper/functions.py new file mode 100644 index 00000000..7220a746 --- /dev/null +++ b/misskaty/helper/functions.py @@ -0,0 +1,109 @@ +from pyrogram import enums +from datetime import datetime, timedelta +from string import ascii_lowercase +from re import findall + + +def get_urls_from_text(text: str) -> bool: + regex = r"""(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-] + [.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|( + \([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\ + ()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".strip() + return [x[0] for x in findall(regex, text)] + + +async def alpha_to_int(user_id_alphabet: str) -> int: + alphabet = list(ascii_lowercase)[:10] + user_id = "" + for i in user_id_alphabet: + index = alphabet.index(i) + user_id += str(index) + return int(user_id) + + +async def int_to_alpha(user_id: int) -> str: + alphabet = list(ascii_lowercase)[:10] + user_id = str(user_id) + return "".join(alphabet[int(i)] for i in user_id) + + +async def extract_userid(message, text: str): + """ + NOT TO BE USED OUTSIDE THIS FILE + """ + + def is_int(text: str): + try: + int(text) + except ValueError: + return False + return True + + text = text.strip() + + if is_int(text): + return int(text) + + entities = message.entities + app = message._client + if len(entities) < 2: + return (await app.get_users(text)).id + entity = entities[1] + if entity.type == enums.MessageEntityType.MENTION: + return (await app.get_users(text)).id + if entity.type == enums.MessageEntityType.MENTION: + return entity.user.id + return None + + +async def extract_user_and_reason(message, sender_chat=False): + args = message.text.strip().split() + text = message.text + user = None + reason = None + if message.reply_to_message: + reply = message.reply_to_message + # if reply to a message and no reason is given + if reply.from_user: + id_ = reply.from_user.id + + elif reply.sender_chat and reply.sender_chat != message.chat.id and sender_chat: + id_ = reply.sender_chat.id + else: + return None, None + reason = None if len(args) < 2 else text.split(None, 1)[1] + return id_, reason + + # if not reply to a message and no reason is given + if len(args) == 2: + user = text.split(None, 1)[1] + return await extract_userid(message, user), None + + # if reason is given + if len(args) > 2: + user, reason = text.split(None, 2)[1:] + return await extract_userid(message, user), reason + + return user, reason + + +async def extract_user(message): + return (await extract_user_and_reason(message))[0] + + +async def time_converter(message, time_value: str) -> int: + unit = ["m", "h", "d"] # m == minutes | h == hours | d == days + check_unit = "".join(list(filter(time_value[-1].lower().endswith, unit))) + currunt_time = datetime.now() + time_digit = time_value[:-1] + if not time_digit.isdigit(): + return await message.reply_text("Incorrect time specified") + if check_unit == "m": + temp_time = currunt_time + timedelta(minutes=int(time_digit)) + elif check_unit == "h": + temp_time = currunt_time + timedelta(hours=int(time_digit)) + elif check_unit == "d": + temp_time = currunt_time + timedelta(days=int(time_digit)) + else: + return await message.reply_text("Incorrect time specified.") + return int(datetime.timestamp(temp_time)) diff --git a/misskaty/helper/http.py b/misskaty/helper/http.py new file mode 100644 index 00000000..1d459cca --- /dev/null +++ b/misskaty/helper/http.py @@ -0,0 +1,78 @@ +""" +MIT License +Copyright (c) 2021 TheHamkerCat +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import httpx +from asyncio import gather +from aiohttp import ClientSession + +# Aiohttp Async Client +session = ClientSession() + +# HTTPx Async Client +http = httpx.AsyncClient( + http2=True, + timeout=httpx.Timeout(40), +) + + +async def get(url: str, *args, **kwargs): + async with session.get(url, *args, **kwargs) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data + + +async def head(url: str, *args, **kwargs): + async with session.head(url, *args, **kwargs) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data + + +async def post(url: str, *args, **kwargs): + async with session.post(url, *args, **kwargs) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data + + +async def multiget(url: str, times: int, *args, **kwargs): + return await gather(*[get(url, *args, **kwargs) for _ in range(times)]) + + +async def multihead(url: str, times: int, *args, **kwargs): + return await gather(*[head(url, *args, **kwargs) for _ in range(times)]) + + +async def multipost(url: str, times: int, *args, **kwargs): + return await gather(*[post(url, *args, **kwargs) for _ in range(times)]) + + +async def resp_get(url: str, *args, **kwargs): + return await session.get(url, *args, **kwargs) + + +async def resp_post(url: str, *args, **kwargs): + return await session.post(url, *args, **kwargs) diff --git a/misskaty/helper/human_read.py b/misskaty/helper/human_read.py new file mode 100644 index 00000000..9050aa49 --- /dev/null +++ b/misskaty/helper/human_read.py @@ -0,0 +1,54 @@ +SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"] + + +def get_readable_file_size(size_in_bytes) -> str: + if size_in_bytes is None: + return "0B" + index = 0 + while size_in_bytes >= 1024: + size_in_bytes /= 1024 + index += 1 + try: + return f"{round(size_in_bytes, 2)}{SIZE_UNITS[index]}" + except IndexError: + return "File too large" + + +def get_readable_time(seconds: int) -> str: + result = "" + (days, remainder) = divmod(seconds, 86400) + days = int(days) + if days != 0: + result += f"{days} day " + (hours, remainder) = divmod(remainder, 3600) + hours = int(hours) + if hours != 0: + result += f"{hours} hour " + (minutes, seconds) = divmod(remainder, 60) + minutes = int(minutes) + if minutes != 0: + result += f"{minutes} minute " + seconds = int(seconds) + result += f"{seconds} second " + return result + + +def get_readable_time2(seconds: int) -> str: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["second", "minute", "hour", "day", "week", "month", "year"] + while count < 4: + count += 1 + remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + for i in range(len(time_list)): + time_list[i] = str(time_list[i]) + time_suffix_list[i] + if len(time_list) == 4: + ping_time += f"{time_list.pop()}, " + time_list.reverse() + ping_time += ":".join(time_list) + return ping_time diff --git a/misskaty/helper/media_helper.py b/misskaty/helper/media_helper.py new file mode 100644 index 00000000..4c316f95 --- /dev/null +++ b/misskaty/helper/media_helper.py @@ -0,0 +1,67 @@ +import os +import asyncio +from html_telegraph_poster import TelegraphPoster +from typing import Tuple +import shlex + + +def post_to_telegraph(a_title: str, content: str) -> str: + """Create a Telegram Post using HTML Content""" + post_client = TelegraphPoster(use_api=True) + auth_name = "MissKaty Bot" + post_client.create_api_token(auth_name) + post_page = post_client.post( + title=a_title, + author=auth_name, + author_url="https://www.yasir.my.id", + text=content, + ) + return post_page["url"] + + +async def run_subprocess(cmd): + process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + return await process.communicate() + + +async def get_media_info(file_link): + ffprobe_cmd = [ + "ffprobe", + "-headers", + "IAM:''", + "-i", + file_link, + "-v", + "quiet", + "-of", + "json", + "-show_streams", + "-show_format", + "-show_chapters", + "-show_programs", + ] + data, err = await run_subprocess(ffprobe_cmd) + return data + + +async def runcmd(cmd: str) -> Tuple[str, str, int, int]: + """run command in terminal""" + args = shlex.split(cmd) + process = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + stdout, stderr = await process.communicate() + return ( + stdout.decode("utf-8", "replace").strip(), + stderr.decode("utf-8", "replace").strip(), + process.returncode, + process.pid, + ) + + +# Solves ValueError: No closing quotation by removing ' or " in file name +def safe_filename(path_): + if path_ is None: + return + safename = path_.replace("'", "").replace('"', "") + if safename != path_: + os.rename(path_, safename) + return safename diff --git a/misskaty/helper/misc.py b/misskaty/helper/misc.py new file mode 100644 index 00000000..e45bf0e6 --- /dev/null +++ b/misskaty/helper/misc.py @@ -0,0 +1,82 @@ +from math import ceil + +from pyrogram.types import InlineKeyboardButton + +from misskaty import MOD_LOAD, MOD_NOLOAD + + +class EqInlineKeyboardButton(InlineKeyboardButton): + def __eq__(self, other): + return self.text == other.text + + def __lt__(self, other): + return self.text < other.text + + def __gt__(self, other): + return self.text > other.text + + +def paginate_modules(page_n, module_dict, prefix, chat=None): + modules = ( + sorted( + [ + EqInlineKeyboardButton( + x.__MODULE__, + callback_data=f"{prefix}_module({chat},{x.__MODULE__.lower()})", + ) + for x in module_dict.values() + ] + ) + if chat + else sorted( + [ + EqInlineKeyboardButton( + x.__MODULE__, + callback_data=f"{prefix}_module({x.__MODULE__.lower()})", + ) + for x in module_dict.values() + ] + ) + ) + + pairs = list(zip(modules[::3], modules[1::3], modules[2::3])) + i = 0 + for m in pairs: + for _ in m: + i += 1 + if len(modules) - i == 1: + pairs.append((modules[-1],)) + elif len(modules) - i == 2: + pairs.append( + ( + modules[-2], + modules[-1], + ) + ) + + COLUMN_SIZE = 4 + + max_num_pages = ceil(len(pairs) / COLUMN_SIZE) + modulo_page = page_n % max_num_pages + + # can only have a certain amount of buttons side by side + if len(pairs) > COLUMN_SIZE: + pairs = pairs[modulo_page * COLUMN_SIZE : COLUMN_SIZE * (modulo_page + 1)] + [ + ( + EqInlineKeyboardButton( + "❮", callback_data=f"{prefix}_prev({modulo_page})" + ), + EqInlineKeyboardButton( + "Back", callback_data=f"{prefix}_home({modulo_page})" + ), + EqInlineKeyboardButton( + "❯", callback_data=f"{prefix}_next({modulo_page})" + ), + ) + ] + + return pairs + + +def is_module_loaded(name): + return (not MOD_LOAD or name in MOD_LOAD) and name not in MOD_NOLOAD diff --git a/misskaty/helper/pyro_progress.py b/misskaty/helper/pyro_progress.py new file mode 100644 index 00000000..7332a01d --- /dev/null +++ b/misskaty/helper/pyro_progress.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# (c) Shrimadhav U K + +import asyncio +import math +import time +from pyrogram.errors import MessageNotModified, FloodWait + + +async def progress_for_pyrogram(current, total, ud_type, message, start): + """generic progress display for Telegram Upload / Download status""" + now = time.time() + diff = now - start + if round(diff % 10.00) == 0 or current == total: + # if round(current / total * 100, 0) % 5 == 0: + percentage = current * 100 / total + elapsed_time = round(diff) + if elapsed_time == 0: + return + speed = current / diff + time_to_completion = round((total - current) / speed) + estimated_total_time = elapsed_time + time_to_completion + + elapsed_time = time_formatter(elapsed_time) + estimated_total_time = time_formatter(estimated_total_time) + + progress = "[{0}{1}] \nP: {2}%\n".format( + "".join(["█" for _ in range(math.floor(percentage / 5))]), + "".join(["░" for _ in range(20 - math.floor(percentage / 5))]), + round(percentage, 2), + ) + + tmp = progress + "{0} of {1}\nSpeed: {2}/s\nETA: {3}\n".format( + humanbytes(current), + humanbytes(total), + humanbytes(speed), + # elapsed_time if elapsed_time != "" else "0 s", + estimated_total_time if estimated_total_time != "" else "0 s", + ) + try: + await message.edit(f"{ud_type}\n {tmp}") + except FloodWait as e: + await asyncio.sleep(e.value) + await message.edit(f"{ud_type}\n {tmp}") + except MessageNotModified: + pass + + +def humanbytes(size: int) -> str: + """converts bytes into human readable format""" + # https://stackoverflow.com/a/49361727/4723940 + # 2**10 = 1024 + if not size: + return "" + power = 2**10 + number = 0 + dict_power_n = {0: " ", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + number += 1 + return f"{str(round(size, 2))} {dict_power_n[number]}B" + + +def time_formatter(seconds: int) -> str: + result = "" + v_m = 0 + remainder = seconds + r_ange_s = {"days": (24 * 60 * 60), "hours": (60 * 60), "minutes": 60, "seconds": 1} + for age, divisor in r_ange_s.items(): + v_m, remainder = divmod(remainder, divisor) + v_m = int(v_m) + if v_m != 0: + result += f" {v_m} {age} " + return result diff --git a/misskaty/helper/stickerset.py b/misskaty/helper/stickerset.py new file mode 100644 index 00000000..a1e8bca7 --- /dev/null +++ b/misskaty/helper/stickerset.py @@ -0,0 +1,73 @@ +""" +MIT License +Copyright (c) 2021 TheHamkerCat +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +from typing import List + +from pyrogram import Client, errors, raw + + +async def get_sticker_set_by_name(client: Client, name: str) -> raw.base.messages.StickerSet: + try: + return await client.invoke( + raw.functions.messages.GetStickerSet( + stickerset=raw.types.InputStickerSetShortName(short_name=name), + hash=0, + ) + ) + except errors.exceptions.not_acceptable_406.StickersetInvalid: + return None + + +# Known errors: (I don't see a reason to catch them as we, for sure, won't face them right now): +# errors.exceptions.bad_request_400.PackShortNameInvalid -> pack name needs to end with _by_botname +# errors.exceptions.bad_request_400.ShortnameOccupyFailed -> pack's name is already in use + + +async def create_sticker_set( + client: Client, + owner: int, + title: str, + short_name: str, + stickers: List[raw.base.InputStickerSetItem], +) -> raw.base.messages.StickerSet: + return await client.invoke( + raw.functions.stickers.CreateStickerSet( + user_id=await client.resolve_peer(owner), + title=title, + short_name=short_name, + stickers=stickers, + ) + ) + + +async def add_sticker_to_set( + client: Client, + stickerset: raw.base.messages.StickerSet, + sticker: raw.base.InputStickerSetItem, +) -> raw.base.messages.StickerSet: + return await client.invoke( + raw.functions.stickers.AddStickerToSet( + stickerset=raw.types.InputStickerSetShortName(short_name=stickerset.set.short_name), + sticker=sticker, + ) + ) + + +async def create_sticker(sticker: raw.base.InputDocument, emoji: str) -> raw.base.InputStickerSetItem: + return raw.types.InputStickerSetItem(document=sticker, emoji=emoji) diff --git a/misskaty/helper/time_gap.py b/misskaty/helper/time_gap.py new file mode 100644 index 00000000..ace9e29d --- /dev/null +++ b/misskaty/helper/time_gap.py @@ -0,0 +1,18 @@ +import time + +GAP = {} + + +async def check_time_gap(user_id: int): + """A Function for checking user time gap! + :parameter user_id Telegram User ID""" + + if str(user_id) in GAP: + current_time = time.time() + previous_time = GAP[str(user_id)] + if round(current_time - previous_time) < 10: + return True, round(previous_time - current_time + 10) + del GAP[str(user_id)] + else: + GAP[str(user_id)] = time.time() + return False, None diff --git a/misskaty/helper/tools.py b/misskaty/helper/tools.py new file mode 100644 index 00000000..5f1a0795 --- /dev/null +++ b/misskaty/helper/tools.py @@ -0,0 +1,83 @@ +import random +import string +import psutil +import time +import os +from misskaty import botStartTime +from misskaty.plugins import ALL_MODULES +from misskaty.helper.human_read import get_readable_time +from misskaty.helper.http import http +from http.cookies import SimpleCookie + +GENRES_EMOJI = { + "Action": "👊", + "Adventure": random.choice(["🪂", "🧗‍♀", "🌋"]), + "Family": "👨‍", + "Musical": "🎸", + "Comedy": "🤣", + "Drama": " 🎭", + "Ecchi": random.choice(["💋", "🥵"]), + "Fantasy": random.choice(["🧞", "🧞‍♂", "🧞‍♀", "🌗"]), + "Hentai": "🔞", + "History": "📜", + "Horror": "☠", + "Mahou Shoujo": "☯", + "Mecha": "🤖", + "Music": "🎸", + "Mystery": "🔮", + "Psychological": "♟", + "Romance": "💞", + "Sci-Fi": "🛸", + "Slice of Life": random.choice(["☘", "🍁"]), + "Sports": "⚽️", + "Supernatural": "🫧", + "Thriller": random.choice(["🥶", "🔪", "🤯"]), +} + + +async def bot_sys_stats(): + bot_uptime = int(time.time() - botStartTime) + cpu = psutil.cpu_percent(interval=0.5) + mem = psutil.virtual_memory().percent + disk = psutil.disk_usage("/").percent + process = psutil.Process(os.getpid()) + return f""" +YasirArisM@MissKatyRoBot +------------------ +UPTIME: {get_readable_time(bot_uptime)} +BOT: {round(process.memory_info()[0] / 1024**2)} MB +CPU: {cpu}% +RAM: {mem}% +DISK: {disk}% + +TOTAL PLUGINS: {len(ALL_MODULES)} +""" + + +def get_random_string(length): + # choose from all lowercase letter + letters = string.ascii_lowercase + return "".join(random.choice(letters) for _ in range(length)) + + +async def rentry(teks): + # buat dapetin cookie + cookie = SimpleCookie() + kuki = (await http.get("https://rentry.co")).cookies + cookie.load(kuki) + kukidict = {key: value.value for key, value in cookie.items()} + # headernya + header = {"Referer": "https://rentry.co"} + payload = {"csrfmiddlewaretoken": kukidict["csrftoken"], "text": teks} + return ( + ( + await http.post( + "https://rentry.co/api/new", + data=payload, + headers=header, + cookies=kukidict, + ) + ) + .json() + .get("url") + ) diff --git a/misskaty/helper/ytdl_helper.py b/misskaty/helper/ytdl_helper.py new file mode 100644 index 00000000..7a0c877e --- /dev/null +++ b/misskaty/helper/ytdl_helper.py @@ -0,0 +1,42 @@ +import string +import os +import requests +import time +import random +from misskaty.helper.human_read import get_readable_file_size + + +def random_char(y): + return "".join(random.choice(string.ascii_letters) for _ in range(y)) + + +def DetectFileSize(url): + r = requests.get(url, allow_redirects=True, stream=True) + return int(r.headers.get("content-length", 0)) + + +def DownLoadFile(url, file_name, chunk_size, client, ud_type, message_id, chat_id): + if os.path.exists(file_name): + os.remove(file_name) + if not url: + return file_name + r = requests.get(url, allow_redirects=True, stream=True) + # https://stackoverflow.com/a/47342052/4723940 + total_size = int(r.headers.get("content-length", 0)) + downloaded_size = 0 + with open(file_name, "wb") as fd: + for chunk in r.iter_content(chunk_size=chunk_size): + if chunk: + fd.write(chunk) + downloaded_size += chunk_size + if client is not None and ((total_size // downloaded_size) % 5) == 0: + time.sleep(0.3) + try: + client.edit_message_text( + chat_id, + message_id, + text=f"{ud_type}: {get_readable_file_size(downloaded_size)} of {get_readable_file_size(total_size)}", + ) + except: + pass + return file_name diff --git a/misskaty/plugins/__init__.py b/misskaty/plugins/__init__.py new file mode 100644 index 00000000..25fde160 --- /dev/null +++ b/misskaty/plugins/__init__.py @@ -0,0 +1,45 @@ +""" + * @author yasir + * @date 2022-12-01 09:12:27 + * @lastModified 2022-12-01 09:27:31 + * @projectName MissKatyPyro + * Copyright ©YasirPedia All rights reserved + """ +import glob +import importlib +import sys +from logging import info as log_info +from os.path import basename, dirname, isfile +from misskaty import MOD_LOAD, MOD_NOLOAD + + +def __list_all_modules(): + # This generates a list of modules in this + # folder for the * in __main__ to work. + mod_paths = glob.glob(f"{dirname(__file__)}/*.py") + all_modules = [ + basename(f)[:-3] for f in mod_paths if isfile(f) and f.endswith(".py") + and not f.endswith("__init__.py") and not f.endswith("__main__.py") + ] + + if MOD_LOAD or MOD_NOLOAD: + to_load = MOD_LOAD + if to_load: + if not all( + any(mod == module_name for module_name in all_modules) + for mod in to_load): + sys.exit() + + else: + to_load = all_modules + + return [item for item in to_load + if item not in MOD_NOLOAD] if MOD_NOLOAD else to_load + + return all_modules + + +log_info("[INFO]: IMPORTING PLUGINS") +importlib.import_module("misskaty.plugins.__main__") +ALL_MODULES = sorted(__list_all_modules()) +__all__ = ALL_MODULES + ["ALL_MODULES"] diff --git a/misskaty/plugins/__main__.py b/misskaty/plugins/__main__.py new file mode 100644 index 00000000..e69de29b diff --git a/misskaty/plugins/admin.py b/misskaty/plugins/admin.py new file mode 100644 index 00000000..bbfa3c56 --- /dev/null +++ b/misskaty/plugins/admin.py @@ -0,0 +1,715 @@ +import logging +import asyncio, re +from misskaty import app +from misskaty.helper.functions import ( + extract_user_and_reason, + time_converter, + extract_user, + int_to_alpha, +) +from time import time +from pyrogram import filters, enums +from pyrogram.errors import FloodWait, ChatAdminRequired +from pyrogram.types import ChatPermissions +from misskaty.core.decorator.permissions import ( + adminsOnly, + admins_in_chat, + list_admins, + member_permissions, +) +from misskaty.core.decorator.errors import capture_err +from misskaty.core.keyboard import ikb +from misskaty.vars import SUDO, COMMAND_HANDLER +from database.warn_db import get_warn, remove_warns, add_warn + +__MODULE__ = "Admin" +__HELP__ = """ +/ban - Ban A User From A Group +/dban - Delete the replied message banning its sender +/tban - Ban A User For Specific Time +/unban - Unban A User +/listban - Ban a user from groups listed in a message +/listunban - Unban a user from groups listed in a message +/warn - Warn A User +/dwarn - Delete the replied message warning its sender +/rmwarns - Remove All Warning of A User +/warns - Show Warning Of A User +/kick - Kick A User +/dkick - Delete the replied message kicking its sender +/purge - Purge Messages +/purge [n] - Purge "n" number of messages from replied message +/del - Delete Replied Message +/promote - Promote A Member +/fullpromote - Promote A Member With All Rights +/demote - Demote A Member +/pin - Pin A Message +/mute - Mute A User +/tmute - Mute A User For Specific Time +/unmute - Unmute A User +/ban_ghosts - Ban Deleted Accounts +/report | @admins | @admin - Report A Message To Admins. +""" + + +# Admin cache reload +@app.on_chat_member_updated() +async def admin_cache_func(_, cmu): + if cmu.old_chat_member and cmu.old_chat_member.promoted_by: + admins_in_chat[cmu.chat.id] = { + "last_updated_at": time(), + "data": [ + member.user.id + async for member in app.get_chat_members( + cmu.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS + ) + ], + } + logging.info(f"Updated admin cache for {cmu.chat.id} [{cmu.chat.title}]") + + +# Purge CMD +@app.on_message(filters.command("purge", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_delete_messages") +async def purge(_, message): + repliedmsg = message.reply_to_message + await message.delete() + + if not repliedmsg: + return await message.reply_text("Reply to a message to purge from.") + + cmd = message.command + if len(cmd) > 1 and cmd[1].isdigit(): + purge_to = repliedmsg.id + int(cmd[1]) + if purge_to > message.id: + purge_to = message.id + else: + purge_to = message.id + + chat_id = message.chat.id + message_ids = [] + del_total = 0 + + for message_id in range( + repliedmsg.id, + purge_to, + ): + message_ids.append(message_id) + + # Max message deletion limit is 100 + if len(message_ids) == 100: + await app.delete_messages( + chat_id=chat_id, + message_ids=message_ids, + revoke=True, # For both sides + ) + del_total += len(message_ids) + # To delete more than 100 messages, start again + message_ids = [] + + # Delete if any messages left + if len(message_ids) > 0: + await app.delete_messages( + chat_id=chat_id, + message_ids=message_ids, + revoke=True, + ) + del_total += len(message_ids) + await message.reply(f"Successfully deleted {del_total} messages..") + + +# Kick members +@app.on_message(filters.command(["kick", "dkick"], COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def kickFunc(_, message): + user_id, reason = await extract_user_and_reason(message) + if not user_id: + return await message.reply_text("I can't find that user.") + if user_id == 1507530289: + return await message.reply_text("I can't kick myself, i can leave if you want.") + if user_id in SUDO: + return await message.reply_text("Wow, you wanna kick my owner?") + if user_id in (await list_admins(message.chat.id)): + return await message.reply_text("Lol, it's crazy if i can kick an admin.") + mention = (await app.get_users(user_id)).mention + msg = f""" +**Kicked User:** {mention} +**Kicked By:** {message.from_user.mention if message.from_user else 'Anon'} +**Reason:** {reason or '-'}""" + if message.command[0][0] == "d": + await message.reply_to_message.delete() + await message.chat.ban_member(user_id) + await message.reply_text(msg) + await asyncio.sleep(1) + await message.chat.unban_member(user_id) + + +# Ban/DBan/TBan User +@app.on_message( + filters.command(["ban", "dban", "tban"], COMMAND_HANDLER) & ~filters.private +) +@adminsOnly("can_restrict_members") +async def banFunc(_, message): + user_id, reason = await extract_user_and_reason(message, sender_chat=True) + + if not user_id: + return await message.reply_text("I can't find that user.") + if user_id == 1507530289: + return await message.reply_text("I can't ban myself, i can leave if you want.") + if user_id in SUDO: + return await message.reply_text("You Wanna Ban The Elevated One?, RECONSIDER!") + if user_id in (await list_admins(message.chat.id)): + return await message.reply_text( + "I can't ban an admin, You know the rules, so do i." + ) + + try: + mention = (await app.get_users(user_id)).mention + except IndexError: + mention = ( + message.reply_to_message.sender_chat.title + if message.reply_to_message + else "Anon" + ) + + msg = ( + f"**Banned User:** {mention}\n" + f"**Banned By:** {message.from_user.mention if message.from_user else 'Anon'}\n" + ) + if message.command[0][0] == "d": + await message.reply_to_message.delete() + if message.command[0] == "tban": + split = reason.split(None, 1) + time_value = split[0] + temp_reason = split[1] if len(split) > 1 else "" + temp_ban = await time_converter(message, time_value) + msg += f"**Banned For:** {time_value}\n" + if temp_reason: + msg += f"**Reason:** {temp_reason}" + try: + if len(time_value[:-1]) < 3: + await message.chat.ban_member(user_id, until_date=temp_ban) + await message.reply_text(msg) + else: + await message.reply_text("You can't use more than 99") + except AttributeError: + pass + return + if reason: + msg += f"**Reason:** {reason}" + keyboard = ikb({"🚨 Unban 🚨": f"unban_{user_id}"}) + await message.chat.ban_member(user_id) + await message.reply_text(msg, reply_markup=keyboard) + + +# Unban members +@app.on_message(filters.command("unban", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def unban_func(_, message): + # we don't need reasons for unban, also, we + # don't need to get "text_mention" entity, because + # normal users won't get text_mention if the user + # they want to unban is not in the group. + reply = message.reply_to_message + + if reply and reply.sender_chat and reply.sender_chat != message.chat.id: + return await message.reply_text("You cannot unban a channel") + + if len(message.command) == 2: + user = message.text.split(None, 1)[1] + elif len(message.command) == 1 and reply: + user = message.reply_to_message.from_user.id + else: + return await message.reply_text( + "Provide a username or reply to a user's message to unban." + ) + await message.chat.unban_member(user) + umention = (await app.get_users(user)).mention + await message.reply_text(f"Unbanned! {umention}") + + +# Ban users listed in a message +@app.on_message( + filters.user(SUDO) & filters.command("listban", COMMAND_HANDLER) & ~filters.private +) +async def list_ban_(c, message): + userid, msglink_reason = await extract_user_and_reason(message) + if not userid or not msglink_reason: + return await message.reply_text( + "Provide a userid/username along with message link and reason to list-ban" + ) + if len(msglink_reason.split(" ")) == 1: # message link included with the reason + return await message.reply_text("You must provide a reason to list-ban") + # seperate messge link from reason + lreason = msglink_reason.split() + messagelink, reason = lreason[0], " ".join(lreason[1:]) + + if not re.search( + r"(https?://)?t(elegram)?\.me/\w+/\d+", messagelink + ): # validate link + return await message.reply_text("Invalid message link provided") + + if userid == 1507530289: + return await message.reply_text("I can't ban myself.") + if userid in SUDO: + return await message.reply_text("You Wanna Ban The Elevated One?, RECONSIDER!") + splitted = messagelink.split("/") + uname, mid = splitted[-2], int(splitted[-1]) + m = await message.reply_text( + "`Banning User from multiple groups. This may take some time`" + ) + try: + msgtext = (await app.get_messages(uname, mid)).text + gusernames = re.findall("@\w+", msgtext) + except: + return await m.edit_text("Could not get group usernames") + count = 0 + for username in gusernames: + try: + await app.ban_chat_member(username.strip("@"), userid) + await asyncio.sleep(1) + except FloodWait as e: + await asyncio.sleep(e.value) + except: + continue + count += 1 + mention = (await app.get_users(userid)).mention + + msg = f""" +**List-Banned User:** {mention} +**Banned User ID:** `{userid}` +**Admin:** {message.from_user.mention} +**Affected chats:** `{count}` +**Reason:** {reason} +""" + await m.edit_text(msg) + + +# Unban users listed in a message +@app.on_message( + filters.user(SUDO) + & filters.command("listunban", COMMAND_HANDLER) + & ~filters.private +) +async def list_unban_(c, message): + userid, msglink = await extract_user_and_reason(message) + if not userid or not msglink: + return await message.reply_text( + "Provide a userid/username along with message link to list-unban" + ) + + if not re.search(r"(https?://)?t(elegram)?\.me/\w+/\d+", msglink): # validate link + return await message.reply_text("Invalid message link provided") + + splitted = msglink.split("/") + uname, mid = splitted[-2], int(splitted[-1]) + m = await message.reply_text( + "`Unbanning User from multiple groups. \ + This may take some time`" + ) + try: + msgtext = (await app.get_messages(uname, mid)).text + gusernames = re.findall("@\w+", msgtext) + except: + return await m.edit_text("Could not get the group usernames") + count = 0 + for username in gusernames: + try: + await app.unban_chat_member(username.strip("@"), userid) + await asyncio.sleep(1) + except FloodWait as e: + await asyncio.sleep(e.x) + except: + continue + count += 1 + mention = (await app.get_users(userid)).mention + msg = f""" +**List-Unbanned User:** {mention} +**Unbanned User ID:** `{userid}` +**Admin:** {message.from_user.mention} +**Affected chats:** `{count}` +""" + await m.edit_text(msg) + + +# Delete messages +@app.on_message(filters.command("del", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_delete_messages") +async def deleteFunc(_, message): + if not message.reply_to_message: + return await message.reply_text("Reply To A Message To Delete It") + await message.reply_to_message.delete() + await message.delete() + + +# Promote Members +@app.on_message( + filters.command(["promote", "fullpromote"], COMMAND_HANDLER) & ~filters.private +) +@adminsOnly("can_promote_members") +async def promoteFunc(_, message): + user_id = await extract_user(message) + umention = (await app.get_users(user_id)).mention + if not user_id: + return await message.reply_text("I can't find that user.") + bot = await app.get_chat_member(message.chat.id, 1507530289) + if user_id == 1507530289: + return await message.reply_text("I can't promote myself.") + if not bot.can_promote_members: + return await message.reply_text("I don't have enough permissions") + if message.command[0][0] == "f": + await message.chat.promote_member( + user_id=user_id, + can_change_info=bot.can_change_info, + can_invite_users=bot.can_invite_users, + can_delete_messages=bot.can_delete_messages, + can_restrict_members=bot.can_restrict_members, + can_pin_messages=bot.can_pin_messages, + can_promote_members=bot.can_promote_members, + can_manage_chat=bot.can_manage_chat, + can_manage_voice_chats=bot.can_manage_voice_chats, + ) + return await message.reply_text(f"Fully Promoted! {umention}") + + await message.chat.promote_member( + user_id=user_id, + can_change_info=False, + can_invite_users=bot.can_invite_users, + can_delete_messages=bot.can_delete_messages, + can_restrict_members=False, + can_pin_messages=False, + can_promote_members=False, + can_manage_chat=bot.can_manage_chat, + can_manage_voice_chats=bot.can_manage_voice_chats, + ) + await message.reply_text(f"Promoted! {umention}") + + +# Demote Member +@app.on_message(filters.command("demote", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_promote_members") +async def demote(_, message): + user_id = await extract_user(message) + if not user_id: + return await message.reply_text("I can't find that user.") + if user_id == 1507530289: + return await message.reply_text("I can't demote myself.") + if user_id in SUDO: + return await message.reply_text("You wanna demote the elevated one?") + await message.chat.promote_member( + user_id=user_id, + can_change_info=False, + can_invite_users=False, + can_delete_messages=False, + can_restrict_members=False, + can_pin_messages=False, + can_promote_members=False, + can_manage_chat=False, + can_manage_voice_chats=False, + ) + umention = (await app.get_users(user_id)).mention + await message.reply_text(f"Demoted! {umention}") + + +# Pin Messages +@app.on_message(filters.command(["pin", "unpin"], COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_pin_messages") +async def pin(_, message): + if not message.reply_to_message: + return await message.reply_text("Reply to a message to pin/unpin it.") + r = message.reply_to_message + try: + if message.command[0][0] == "u": + await r.unpin() + return await message.reply_text( + f"**Unpinned [this]({r.link}) message.**", + disable_web_page_preview=True, + ) + await r.pin(disable_notification=True) + await message.reply( + f"**Pinned [this]({r.link}) message.**", + disable_web_page_preview=True, + ) + except ChatAdminRequired: + await message.reply( + "Please give me admin access to use this command.", + disable_web_page_preview=True, + ) + + +# Mute members +@app.on_message(filters.command(["mute", "tmute"], COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def mute(_, message): + user_id, reason = await extract_user_and_reason(message) + if not user_id: + return await message.reply_text("I can't find that user.") + if user_id == 1507530289: + return await message.reply_text("I can't mute myself.") + if user_id in SUDO: + return await message.reply_text("You wanna mute the elevated one?, RECONSIDER!") + if user_id in (await list_admins(message.chat.id)): + return await message.reply_text( + "I can't mute an admin, You know the rules, so do i." + ) + mention = (await app.get_users(user_id)).mention + keyboard = ikb({"🚨 Unmute 🚨": f"unmute_{user_id}"}) + msg = ( + f"**Muted User:** {mention}\n" + f"**Muted By:** {message.from_user.mention if message.from_user else 'Anon'}\n" + ) + if message.command[0] == "tmute": + split = reason.split(None, 1) + time_value = split[0] + temp_reason = split[1] if len(split) > 1 else "" + temp_mute = await time_converter(message, time_value) + msg += f"**Muted For:** {time_value}\n" + if temp_reason: + msg += f"**Reason:** {temp_reason}" + try: + if len(time_value[:-1]) < 3: + await message.chat.restrict_member( + user_id, + permissions=ChatPermissions(), + until_date=temp_mute, + ) + await message.reply_text(msg, reply_markup=keyboard) + else: + await message.reply_text("You can't use more than 99") + except AttributeError: + pass + return + if reason: + msg += f"**Reason:** {reason}" + await message.chat.restrict_member(user_id, permissions=ChatPermissions()) + await message.reply_text(msg, reply_markup=keyboard) + + +# Unmute members +@app.on_message(filters.command("unmute", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def unmute(_, message): + user_id = await extract_user(message) + if not user_id: + return await message.reply_text("I can't find that user.") + await message.chat.unban_member(user_id) + umention = (await app.get_users(user_id)).mention + await message.reply_text(f"Unmuted! {umention}") + + +# Ban deleted accounts +@app.on_message(filters.command("ban_ghosts", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def ban_deleted_accounts(_, message): + chat_id = message.chat.id + deleted_users = [] + m = await message.reply("Finding ghosts...") + + async for i in app.iter_chat_members(chat_id): + if i.user.is_deleted: + deleted_users.append(i.user.id) + if deleted_users: + banned_users = 0 + for deleted_user in deleted_users: + try: + await message.chat.ban_member(deleted_user) + except Exception: + pass + banned_users += 1 + await m.edit(f"Banned {banned_users} Deleted Accounts") + else: + await m.edit("There are no deleted accounts in this chat") + + +@app.on_message(filters.command(["warn", "dwarn"], COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def warn_user(_, message): + user_id, reason = await extract_user_and_reason(message) + chat_id = message.chat.id + if not user_id: + return await message.reply_text("I can't find that user.") + if user_id == 1507530289: + return await message.reply_text("I can't warn myself, i can leave if you want.") + if user_id in SUDO: + return await message.reply_text("You Wanna Warn The Elevated One?, RECONSIDER!") + if user_id in (await list_admins(chat_id)): + return await message.reply_text( + "I can't warn an admin, You know the rules, so do i." + ) + user, warns = await asyncio.gather( + app.get_users(user_id), + get_warn(chat_id, await int_to_alpha(user_id)), + ) + mention = user.mention + keyboard = ikb({"🚨 Remove Warn 🚨": f"unwarn_{user_id}"}) + warns = warns["warns"] if warns else 0 + if message.command[0][0] == "d": + await message.reply_to_message.delete() + if warns >= 2: + await message.chat.ban_member(user_id) + await message.reply_text(f"Number of warns of {mention} exceeded, BANNED!") + await remove_warns(chat_id, await int_to_alpha(user_id)) + else: + warn = {"warns": warns + 1} + msg = f""" +**Warned User:** {mention} +**Warned By:** {message.from_user.mention if message.from_user else 'Anon'} +**Reason:** {reason or 'No Reason Provided.'} +**Warns:** {warns + 1}/3""" + await message.reply_text(msg, reply_markup=keyboard) + await add_warn(chat_id, await int_to_alpha(user_id), warn) + + +@app.on_callback_query(filters.regex("unwarn_")) +async def remove_warning(_, cq): + from_user = cq.from_user + chat_id = cq.message.chat.id + permissions = await member_permissions(chat_id, from_user.id) + permission = "can_restrict_members" + if permission not in permissions: + return await cq.answer( + "You don't have enough permissions to perform this action.\n" + + f"Permission needed: {permission}", + show_alert=True, + ) + user_id = cq.data.split("_")[1] + warns = await get_warn(chat_id, await int_to_alpha(user_id)) + if warns: + warns = warns["warns"] + if not warns or warns == 0: + return await cq.answer("User has no warnings.") + warn = {"warns": warns - 1} + await add_warn(chat_id, await int_to_alpha(user_id), warn) + text = cq.message.text.markdown + text = f"~~{text}~~\n\n" + text += f"__Warn removed by {from_user.mention}__" + await cq.message.edit(text) + + +@app.on_callback_query(filters.regex("unmute_")) +async def unmute_user(_, cq): + from_user = cq.from_user + chat_id = cq.message.chat.id + permissions = await member_permissions(chat_id, from_user.id) + permission = "can_restrict_members" + if permission not in permissions: + return await cq.answer( + "You don't have enough permissions to perform this action.\n" + + f"Permission needed: {permission}", + show_alert=True, + ) + user_id = cq.data.split("_")[1] + text = cq.message.text.markdown + text = f"~~{text}~~\n\n" + text += f"__Mute removed by {from_user.mention}__" + await cq.message.chat.unban_member(user_id) + await cq.message.edit(text) + + +@app.on_callback_query(filters.regex("unban_")) +async def unban_user(_, cq): + from_user = cq.from_user + chat_id = cq.message.chat.id + permissions = await member_permissions(chat_id, from_user.id) + permission = "can_restrict_members" + if permission not in permissions: + return await cq.answer( + "You don't have enough permissions to perform this action.\n" + + f"Permission needed: {permission}", + show_alert=True, + ) + user_id = cq.data.split("_")[1] + (await app.get_users(user_id)).mention + text = cq.message.text.markdown + text = f"~~{text}~~\n\n" + text += f"__Banned removed by {from_user.mention}__" + await cq.message.chat.unban_member(user_id) + await cq.message.edit(text) + + +# Remove Warn +@app.on_message(filters.command("rmwarn", COMMAND_HANDLER) & ~filters.private) +@adminsOnly("can_restrict_members") +async def remove_warnings(_, message): + if not message.reply_to_message: + return await message.reply_text( + "Reply to a message to remove a user's warnings." + ) + user_id = message.reply_to_message.from_user.id + mention = message.reply_to_message.from_user.mention + chat_id = message.chat.id + warns = await get_warn(chat_id, await int_to_alpha(user_id)) + if warns: + warns = warns["warns"] + if warns == 0 or not warns: + await message.reply_text(f"{mention} have no warnings.") + else: + await remove_warns(chat_id, await int_to_alpha(user_id)) + await message.reply_text(f"Removed warnings of {mention}.") + + +# Warns +@app.on_message(filters.command("warns", COMMAND_HANDLER) & ~filters.private) +@capture_err +async def check_warns(_, message): + user_id = await extract_user(message) + if not user_id: + return await message.reply_text("I can't find that user.") + warns = await get_warn(message.chat.id, await int_to_alpha(user_id)) + mention = (await app.get_users(user_id)).mention + if warns: + warns = warns["warns"] + else: + return await message.reply_text(f"{mention} has no warnings.") + return await message.reply_text(f"{mention} has {warns}/3 warnings.") + + +# Report User in Group +@app.on_message( + ( + filters.command("report", COMMAND_HANDLER) + | filters.command(["admins", "admin"], prefixes="@") + ) + & ~filters.private +) +@capture_err +async def report_user(_, message): + if not message.reply_to_message: + return await message.reply_text("Reply to a message to report that user.") + reply = message.reply_to_message + reply_id = reply.from_user.id if reply.from_user else reply.sender_chat.id + user_id = message.from_user.id if message.from_user else message.sender_chat.id + if reply_id == user_id: + return await message.reply_text("Why are you reporting yourself ?") + + list_of_admins = await list_admins(message.chat.id) + linked_chat = (await app.get_chat(message.chat.id)).linked_chat + if linked_chat is None: + if reply_id in list_of_admins or reply_id == message.chat.id: + return await message.reply_text( + "Do you know that the user you are replying is an admin ?" + ) + + elif ( + reply_id in list_of_admins + or reply_id == message.chat.id + or reply_id == linked_chat.id + ): + return await message.reply_text( + "Do you know that the user you are replying is an admin ?" + ) + user_mention = ( + reply.from_user.mention if reply.from_user else reply.sender_chat.title + ) + text = f"Reported {user_mention} to admins!" + admin_data = [ + m + async for m in app.get_chat_members( + message.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS + ) + ] + for admin in admin_data: + if admin.user.is_bot or admin.user.is_deleted: + # return bots or deleted admins + continue + text += f"[\u2063](tg://user?id={admin.user.id})" + await message.reply_to_message.reply_text(text) diff --git a/misskaty/plugins/afk.py b/misskaty/plugins/afk.py new file mode 100644 index 00000000..2a7bd9fd --- /dev/null +++ b/misskaty/plugins/afk.py @@ -0,0 +1,175 @@ +# Sample menggunakan modul motor mongodb +import time, asyncio +from misskaty import app +from pyrogram import filters +from misskaty.vars import COMMAND_HANDLER +from database.afk_db import remove_afk, is_afk, add_afk +from misskaty.helper.human_read import get_readable_time2 +from misskaty.core.decorator.errors import capture_err + +__MODULE__ = "AFK" +__HELP__ = """/afk [Reason > Optional] - Tell others that you are AFK (Away From Keyboard), so that your boyfriend or girlfriend won't look for you 💔. +Just type something in group to remove AFK Status.""" + + +# Handle set AFK Command +@capture_err +@app.on_message(filters.command(["afk"], COMMAND_HANDLER)) +async def active_afk(_, message): + if message.sender_chat: + return + user_id = message.from_user.id + verifier, reasondb = await is_afk(user_id) + if verifier: + await remove_afk(user_id) + try: + afktype = reasondb["type"] + timeafk = reasondb["time"] + data = reasondb["data"] + reasonafk = reasondb["reason"] + seenago = get_readable_time2((int(time.time() - timeafk))) + if afktype == "text": + return await message.reply_text( + f"**{message.from_user.first_name}** is back online and was away for {seenago}", + disable_web_page_preview=True, + ) + if afktype == "text_reason": + return await message.reply_text( + f"**{message.from_user.first_name}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}", + disable_web_page_preview=True, + ) + if afktype == "animation": + return ( + await message.reply_animation( + data, + caption=f"**{message.from_user.first_name}** is back online and was away for {seenago}", + ) + if str(reasonafk) == "None" + else await message.reply_animation( + data, + caption=f"**{message.from_user.first_name}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}", + ) + ) + + elif afktype == "photo": + return ( + await message.reply_photo( + photo=f"downloads/{user_id}.jpg", + caption=f"**{message.from_user.first_name}** is back online and was away for {seenago}", + ) + if str(reasonafk) == "None" + else await message.reply_photo( + photo=f"downloads/{user_id}.jpg", + caption=f"**{message.from_user.first_name}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}", + ) + ) + + except Exception: + return await message.reply_text( + f"**{message.from_user.first_name}** is back online.", + disable_web_page_preview=True, + ) + if len(message.command) == 1 and not message.reply_to_message: + details = { + "type": "text", + "time": time.time(), + "data": None, + "reason": None, + } + elif len(message.command) > 1 and not message.reply_to_message: + _reason = (message.text.split(None, 1)[1].strip())[:100] + details = { + "type": "text_reason", + "time": time.time(), + "data": None, + "reason": _reason, + } + elif len(message.command) == 1 and message.reply_to_message.animation: + _data = message.reply_to_message.animation.file_id + details = { + "type": "animation", + "time": time.time(), + "data": _data, + "reason": None, + } + elif len(message.command) > 1 and message.reply_to_message.animation: + _data = message.reply_to_message.animation.file_id + _reason = (message.text.split(None, 1)[1].strip())[:100] + details = { + "type": "animation", + "time": time.time(), + "data": _data, + "reason": _reason, + } + elif len(message.command) == 1 and message.reply_to_message.photo: + await app.download_media(message.reply_to_message, file_name=f"{user_id}.jpg") + details = { + "type": "photo", + "time": time.time(), + "data": None, + "reason": None, + } + elif len(message.command) > 1 and message.reply_to_message.photo: + await app.download_media(message.reply_to_message, file_name=f"{user_id}.jpg") + _reason = message.text.split(None, 1)[1].strip() + details = { + "type": "photo", + "time": time.time(), + "data": None, + "reason": _reason, + } + elif len(message.command) == 1 and message.reply_to_message.sticker: + if message.reply_to_message.sticker.is_animated: + details = { + "type": "text", + "time": time.time(), + "data": None, + "reason": None, + } + else: + await app.download_media( + message.reply_to_message, file_name=f"{user_id}.jpg" + ) + details = { + "type": "photo", + "time": time.time(), + "data": None, + "reason": None, + } + elif len(message.command) > 1 and message.reply_to_message.sticker: + _reason = (message.text.split(None, 1)[1].strip())[:100] + if message.reply_to_message.sticker.is_animated: + details = { + "type": "text_reason", + "time": time.time(), + "data": None, + "reason": _reason, + } + else: + await app.download_media( + message.reply_to_message, file_name=f"{user_id}.jpg" + ) + details = { + "type": "photo", + "time": time.time(), + "data": None, + "reason": _reason, + } + else: + details = { + "type": "text", + "time": time.time(), + "data": None, + "reason": None, + } + + await add_afk(user_id, details) + pesan = await message.reply_text( + f"{message.from_user.mention} [{message.from_user.id}] is now AFK! This message will be deleted in 10s." + ) + await asyncio.sleep(10) + await pesan.delete() + try: + await message.delete() + except: + pass diff --git a/misskaty/plugins/auto_approve.py b/misskaty/plugins/auto_approve.py new file mode 100644 index 00000000..c476afd1 --- /dev/null +++ b/misskaty/plugins/auto_approve.py @@ -0,0 +1,44 @@ +from misskaty import app +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram.errors import UserIsBlocked, UserAlreadyParticipant +from misskaty.core.decorator.errors import capture_err + + +@capture_err +@app.on_chat_join_request(filters.chat(-1001686184174)) +async def approve_join_chat(c, m): + try: + markup = InlineKeyboardMarkup([[InlineKeyboardButton(text="Sudah", callback_data=f"approve_{m.chat.id}"), InlineKeyboardButton(text="Belum", callback_data=f"declined_{m.chat.id}")]]) + await c.send_message( + m.from_user.id, + "PERMINTAAN JOIN CHANNEL YMOVIEZ REBORN\n\nSebelum masuk ke channel ada tes kejujuran, apakah anda sudah membaca catatan di @YMovieZ_New? Jika sudah silahkan klik Sudah, jika kamu berbohong resiko kamu tanggung sendiri 😶‍🌫️.\n\nBot by @YasirPediaChannel", + disable_web_page_preview=True, + reply_markup=markup, + ) + except UserIsBlocked: + await m.decline() + + +@app.on_callback_query(filters.regex(r"^approve")) +async def approve_chat(c, q): + i, chat = q.data.split("_") + try: + await q.message.edit("Yeayy, selamat kamu bisa bergabung di Channel YMovieZ Reborn...") + await c.approve_chat_join_request(chat, q.from_user.id) + except UserAlreadyParticipant: + await q.message.edit("Kamu sudah di acc join grup, jadi ga perlu menekan button.") + except Exception as err: + await q.message.edit(err) + + +@app.on_callback_query(filters.regex(r"^declined")) +async def decline_chat(c, q): + i, chat = q.data.split("_") + try: + await q.message.edit("Yahh, kamu ditolak join channel. Biasakan rajin membaca yahhh..") + await c.decline_chat_join_request(chat, q.from_user.id) + except UserAlreadyParticipant: + await q.message.edit("Kamu sudah di acc join grup, jadi ga perlu menekan button.") + except Exception as err: + await q.message.edit(err) diff --git a/misskaty/plugins/auto_forwarder.py b/misskaty/plugins/auto_forwarder.py new file mode 100644 index 00000000..c8b0c09d --- /dev/null +++ b/misskaty/plugins/auto_forwarder.py @@ -0,0 +1,93 @@ +# Code copy from https://github.com/AbirHasan2005/Forward-Client +import logging +from misskaty import user +from pyrogram import filters +from asyncio import sleep +from pyrogram.types import Message +from pyrogram.errors import FloodWait +from misskaty.vars import ( + FORWARD_FILTERS, + BLOCK_FILES_WITHOUT_EXTENSIONS, + BLOCKED_EXTENSIONS, + FORWARD_FROM_CHAT_ID, + FORWARD_TO_CHAT_ID, + MINIMUM_FILE_SIZE, +) + + +async def FilterMessage(message: Message): + if (message.forward_from or message.forward_from_chat) and ( + "forwarded" not in FORWARD_FILTERS + ): + return 400 + if (len(FORWARD_FILTERS) == 9) or ( + (message.video and ("video" in FORWARD_FILTERS)) + or (message.document and ("document" in FORWARD_FILTERS)) + or (message.photo and ("photo" in FORWARD_FILTERS)) + or (message.audio and ("audio" in FORWARD_FILTERS)) + or (message.text and ("text" in FORWARD_FILTERS)) + or (message.animation and ("gif" in FORWARD_FILTERS)) + or (message.poll and ("poll" in FORWARD_FILTERS)) + or (message.sticker and ("sticker" in FORWARD_FILTERS)) + ): + return 200 + else: + return 400 + + +async def CheckBlockedExt(event: Message): + media = event.document or event.video or event.audio or event.animation + if (BLOCK_FILES_WITHOUT_EXTENSIONS is True) and ("." not in media.file_name): + return True + if (media is not None) and (media.file_name is not None): + _file = media.file_name.rsplit(".", 1) + if len(_file) == 2: + return ( + _file[-1].lower() in BLOCKED_EXTENSIONS + or _file[-1].upper() in BLOCKED_EXTENSIONS + ) + + else: + return False + + +async def CheckFileSize(msg: Message): + media = msg.video or msg.document or msg.audio or msg.photo or msg.animation + return MINIMUM_FILE_SIZE is None or media.file_size >= int(MINIMUM_FILE_SIZE) + + +async def ForwardMessage(client: user, msg: Message): + try: + ## --- Check 1 --- ## + can_forward = await FilterMessage(message=msg) + if can_forward == 400: + return 400 + ## --- Check 2 --- ## + has_blocked_ext = await CheckBlockedExt(event=msg) + if has_blocked_ext is True: + return 400 + ## --- Check 3 --- ## + file_size_passed = await CheckFileSize(msg=msg) + if file_size_passed is False: + return 400 + ## --- Check 4 --- ## + for i in range(len(FORWARD_TO_CHAT_ID)): + try: + await msg.copy(FORWARD_TO_CHAT_ID[i]) + except FloodWait as e: + await sleep(e.value) + logging.warning(f"#FloodWait: Stopped Forwarder for {e.x}s!") + await ForwardMessage(client, msg) + except Exception as err: + logging.warning( + f"#ERROR: {err}\n\nUnable to Forward Message to {str(FORWARD_TO_CHAT_ID[i])}, reason: {err}" + ) + except Exception as err: + logging.warning(f"#ERROR: {err}") + + +@user.on_message((filters.text | filters.media) & filters.chat(FORWARD_FROM_CHAT_ID)) +async def forwardubot(client: user, message: Message): + try_forward = await ForwardMessage(client, message) + if try_forward == 400: + return diff --git a/misskaty/plugins/banned.py b/misskaty/plugins/banned.py new file mode 100644 index 00000000..6c22eb8f --- /dev/null +++ b/misskaty/plugins/banned.py @@ -0,0 +1,40 @@ +from pyrogram import filters +from utils import temp +from pyrogram.types import Message +from database.users_chats_db import db +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from misskaty.vars import SUPPORT_CHAT +from misskaty import app + + +async def banned_users(_, client, message: Message): + return (message.from_user is not None or not message.sender_chat) and message.from_user.id in temp.BANNED_USERS + + +banned_user = filters.create(banned_users) + + +async def disabled_chat(_, client, message: Message): + return message.chat.id in temp.BANNED_CHATS + + +disabled_group = filters.create(disabled_chat) + + +@app.on_message(filters.private & banned_user & filters.incoming) +async def ban_reply(bot, message): + ban = await db.get_ban_status(message.from_user.id) + await message.reply(f'Sorry Dude, You are Banned to use Me. \nBan Reason: {ban["ban_reason"]}') + + +@app.on_message(filters.group & disabled_group & filters.incoming) +async def grp_bd(bot, message): + buttons = [[InlineKeyboardButton("Support", url=f"https://t.me/{SUPPORT_CHAT}")]] + reply_markup = InlineKeyboardMarkup(buttons) + vazha = await db.get_chat(message.chat.id) + k = await message.reply(text=f"CHAT NOT ALLOWED 🐞\n\nMy admins has restricted me from working here ! If you want to know more about it contact support..\nReason : {vazha['reason']}.", reply_markup=reply_markup) + try: + await k.pin() + except: + pass + await bot.leave_chat(message.chat.id) diff --git a/misskaty/plugins/broadcast.py b/misskaty/plugins/broadcast.py new file mode 100644 index 00000000..ebf0bbba --- /dev/null +++ b/misskaty/plugins/broadcast.py @@ -0,0 +1,44 @@ +from pyrogram import filters +import datetime +import time +from database.users_chats_db import db +from misskaty.vars import SUDO +from utils import broadcast_messages +import asyncio +from misskaty import app + + +@app.on_message(filters.command("broadcast") & filters.user(SUDO) & filters.reply) +async def broadcast(bot, message): + users = await db.get_all_users() + b_msg = message.reply_to_message + sts = await message.reply_text(text="Broadcasting your messages...") + start_time = time.time() + total_users = await db.total_users_count() + done = 0 + blocked = 0 + deleted = 0 + failed = 0 + + success = 0 + async for user in users: + pti, sh = await broadcast_messages(int(user["id"]), b_msg) + if pti: + success += 1 + elif pti == False: + if sh == "Bocked": + blocked += 1 + elif sh == "Deleted": + deleted += 1 + elif sh == "Error": + failed += 1 + done += 1 + await asyncio.sleep(2) + if not done % 20: + await sts.edit( + f"Broadcast in progress:\n\nTotal Users {total_users}\nCompleted: {done} / {total_users}\nSuccess: {success}\nBlocked: {blocked}\nDeleted: {deleted}" + ) + time_taken = datetime.timedelta(seconds=int(time.time() - start_time)) + await sts.edit( + f"Broadcast Completed:\nCompleted in {time_taken} seconds.\n\nTotal Users {total_users}\nCompleted: {done} / {total_users}\nSuccess: {success}\nBlocked: {blocked}\nDeleted: {deleted}" + ) diff --git a/misskaty/plugins/bypass.py b/misskaty/plugins/bypass.py new file mode 100644 index 00000000..a8a08db2 --- /dev/null +++ b/misskaty/plugins/bypass.py @@ -0,0 +1,73 @@ +import re +from misskaty.helper.http import http +from misskaty import app +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram.errors import MessageTooLong, EntitiesTooLong +from misskaty.vars import COMMAND_HANDLER +from misskaty.helper.tools import rentry +from urllib.parse import unquote +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.human_read import get_readable_file_size + +LIST_LINK = """ +- Pling and all aliases. +- Other link soon... +""" + +__MODULE__ = "Bypass" +__HELP__ = f""" +/directurl [Link] - Bypass URL. + +Supported Link: +{LIST_LINK} +""" + + +async def pling_bypass(url): + try: + id_url = re.search(r"https?://(store.kde.org|www.pling.com)\/p\/(\d+)", url)[2] + link = f"https://www.pling.com/p/{id_url}/loadFiles" + res = await http.get(link) + json_dic_files = res.json().pop("files") + msg = f"\n**Source Link** :\n`{url}`\n**Direct Link :**\n" + msg += "\n".join( + f'**→ [{i["name"]}]({unquote(i["url"])}) ({get_readable_file_size(int(i["size"]))})**' + for i in json_dic_files + ) + return msg + except Exception as e: + return e + + +@app.on_message(filters.command(["directurl"], COMMAND_HANDLER)) +@capture_err +async def bypass(_, message): + if len(message.command) == 1: + return await message.reply( + f"Gunakan perintah /{message.command[0]} untuk bypass url" + ) + url = message.command[1] + msg = await message.reply("Bypassing URL..", quote=True) + mention = f"**Bypasser:** {message.from_user.mention} ({message.from_user.id})" + if re.match(r"https?://(store.kde.org|www.pling.com)\/p\/(\d+)", url): + data = await pling_bypass(url) + try: + await msg.edit(f"{data}\n\n{mention}") + except (MessageTooLong, EntitiesTooLong): + result = await rentry(data) + markup = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton("Open Link", url=result), + InlineKeyboardButton("Raw Link", url=f"{result}/raw"), + ] + ] + ) + await msg.edit( + f"{result}\n\nBecause your bypassed url is too long, so your link will be pasted to rentry.\n{mention}", + reply_markup=markup, + disable_web_page_preview=True, + ) + else: + await msg.edit("Unsupported link..") diff --git a/misskaty/plugins/code_tester.py b/misskaty/plugins/code_tester.py new file mode 100644 index 00000000..1367e5e8 --- /dev/null +++ b/misskaty/plugins/code_tester.py @@ -0,0 +1,710 @@ +import aiohttp +from pyrogram import enums, filters +from pyrogram.errors import MessageTooLong +from misskaty import app +from misskaty.helper.tools import rentry +from misskaty.vars import COMMAND_HANDLER + +__MODULE__ = "CodeTester" +__HELP__ = """ +This feature allows you to run multiple programming languages through this bot via the Glot.io api. The following is a list of supported languages, for temporary commands only support with a "!" like the example below. + +List of Supported Programming Languages: +~> assembly +~> ats +~> bash +~> c +~> clojure +~> cobol +~> coffeescript +~> cpp +~> crystal +~> csharp +~> d +~> elixir +~> elm +~> erlang +~> fsharp +~> go +~> groovy +~> haskell +~> idris +~> java +~> javascript +~> julia +~> kotlin +~> lua +~> mercury +~> nim +~> nix +~> ocaml +~> perl +~> php +~> python +~> raku +~> ruby +~> rust +~> scala +~> swift +~> typescript + +**Example:** +~> `!python print("Hai aku MissKatyRoBot")` +""" + + +async def listcode(): + async with aiohttp.ClientSession() as session: + r = await session.get("https://glot.io/api/run") + return await r.json() + + +async def glot(lang, langcode, code): + async with aiohttp.ClientSession() as session: + data = {"files": [{"name": f"misskaty.{langcode}", "content": code}]} + headers = { + "content-type": "application/json", + "Authorization": "Token b8a2b75a-a078-4089-869c-e53d448b1ebb", + } + r = await session.post( + f"https://glot.io/api/run/{lang}/latest", headers=headers, json=data + ) + return await r.json() + + +@app.on_message(filters.command(["codelist"], COMMAND_HANDLER)) +async def list_lang(client, message): + daftarlang = await listcode() + list_ = "".join(f"~> {i['name']}\n" for i in daftarlang) + return await message.reply( + f"List of Supported Programming Languages:\n{list_}" + ) + + +@app.on_message(filters.command(["assembly"], "!")) +@app.on_edited_message(filters.command(["assembly"], "!")) +async def assembly(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "asm", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["ats"], "!")) +@app.on_edited_message(filters.command(["ats"], "!")) +async def ats(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "dats", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["bash"], "!")) +@app.on_edited_message(filters.command(["bash"], "!")) +async def bash(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "sh", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["c"], "!")) +@app.on_edited_message(filters.command(["c"], "!")) +async def c(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "c", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["clojure"], "!")) +@app.on_edited_message(filters.command(["clojure"], "!")) +async def clojure(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "clj", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["cobol"], "!")) +@app.on_edited_message(filters.command(["cobol"], "!")) +async def cobol(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "cob", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["coffeescript"], "!")) +@app.on_edited_message(filters.command(["coffeescript"], "!")) +async def coffeescript(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "coffee", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["cpp"], "!")) +@app.on_edited_message(filters.command(["cpp"], "!")) +async def cpp(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "cpp", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["crystal"], "!")) +@app.on_edited_message(filters.command(["crystal"], "!")) +async def crystal(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "cr", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["csharp"], "!")) +@app.on_edited_message(filters.command(["csharp"], "!")) +async def csharp(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "cs", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["d"], "!")) +@app.on_edited_message(filters.command(["d"], "!")) +async def d(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "d", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["elixir"], "!")) +@app.on_edited_message(filters.command(["elixir"], "!")) +async def elixir(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "ex", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["elm"], "!")) +@app.on_edited_message(filters.command(["elm"], "!")) +async def elm(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "elm", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["erlang"], "!")) +@app.on_edited_message(filters.command(["erlang"], "!")) +async def erlang(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "erl", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["fsharp"], "!")) +@app.on_edited_message(filters.command(["fsharp"], "!")) +async def fsharp(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "fs", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["go"], "!")) +@app.on_edited_message(filters.command(["go"], "!")) +async def go(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "go", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["groovy"], "!")) +@app.on_edited_message(filters.command(["groovy"], "!")) +async def groovy(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "groovy", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["haskell"], "!")) +@app.on_edited_message(filters.command(["haskell"], "!")) +async def haskell(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "hs", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["idris"], "!")) +@app.on_edited_message(filters.command(["idris"], "!")) +async def idris(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "idr", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["java"], "!")) +@app.on_edited_message(filters.command(["java"], "!")) +async def java(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "java", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["javascript"], "!")) +@app.on_edited_message(filters.command(["javascript"], "!")) +async def javascript(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "js", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["julia"], "!")) +@app.on_edited_message(filters.command(["julia"], "!")) +async def julia(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "jl", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["kotlin"], "!")) +@app.on_edited_message(filters.command(["kotlin"], "!")) +async def kotlin(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "kt", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["lua"], "!")) +@app.on_edited_message(filters.command(["lua"], "!")) +async def lua(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "lua", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["mercury"], "!")) +@app.on_edited_message(filters.command(["mercury"], "!")) +async def mercury(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "m", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["nim"], "!")) +@app.on_edited_message(filters.command(["nim"], "!")) +async def nim(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "nim", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["nix"], "!")) +@app.on_edited_message(filters.command(["nix"], "!")) +async def nix(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "nix", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["ocaml"], "!")) +@app.on_edited_message(filters.command(["ocaml"], "!")) +async def ocaml(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "ml", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["perl"], "!")) +@app.on_edited_message(filters.command(["perl"], "!")) +async def perl(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "pl", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["php"], "!")) +@app.on_edited_message(filters.command(["php"], "!")) +async def php(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "php", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["python"], "!")) +@app.on_edited_message(filters.command(["python"], "!")) +async def python(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "py", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["raku"], "!")) +@app.on_edited_message(filters.command(["raku"], "!")) +async def raku(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "raku", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["ruby"], "!")) +@app.on_edited_message(filters.command(["ruby"], "!")) +async def ruby(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "rb", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["rust"], "!")) +@app.on_edited_message(filters.command(["rust"], "!")) +async def rust(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "rs", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["scala"], "!")) +@app.on_edited_message(filters.command(["scala"], "!")) +async def scala(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "scala", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["swift"], "!")) +@app.on_edited_message(filters.command(["swift"], "!")) +async def swift(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "swift", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) + + +@app.on_message(filters.command(["typescript"], "!")) +@app.on_edited_message(filters.command(["typescript"], "!")) +async def typescript(client, message): + if len(message.command) < 2: + return await message.reply("Please enter the code you want to run.") + res = await glot(message.command[0], "ts", message.text.split(None, 1)[1]) + hasil = res["stdout"] or res["stderr"] + hasil = f"Result :\n{hasil}" + try: + return await message.reply(hasil, parse_mode=enums.ParseMode.DISABLED) + except MessageTooLong: + post = await rentry(hasil) + return await message.reply(f"View Result in Rentry:\n{post}") + except Exception as e: + return await message.reply(e, parse_mode=enums.ParseMode.DISABLED) diff --git a/misskaty/plugins/copy_forward.py b/misskaty/plugins/copy_forward.py new file mode 100644 index 00000000..2eeba8b8 --- /dev/null +++ b/misskaty/plugins/copy_forward.py @@ -0,0 +1,77 @@ +from pyrogram import filters, enums +from pyrogram.errors import UserIsBlocked, UserNotParticipant +from misskaty.vars import COMMAND_HANDLER +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from misskaty.core.decorator.errors import capture_err +from misskaty import app + + +@app.on_message(filters.command(["copy"], COMMAND_HANDLER)) +async def copy(client, message): + if len(message.command) == 1: + if not message.reply_to_message: + return await message.reply("Silahkan balas pesan yang mau dicopy.") + try: + await message.reply_to_message.copy(message.from_user.id, caption_entities=message.reply_to_message.entities, reply_markup=message.reply_to_message.reply_markup) + return await message.reply_text("Pesan berhasil dikirim..") + except UserIsBlocked: + return await message.reply("Silahkan PM Saya untuk mengcopy pesan ke chat pribadi..", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="💬 Chat Aku Yahh", url="https://t.me/MissKatyRoBot")]])) + except Exception as e: + return await message.reply(f"ERROR: {str(e)}") + elif message.reply_to_message: + try: + idtujuan = message.command[1] + userstat = await app.get_chat_member(-1001686184174, message.from_user.id) + if ( + userstat.status + not in [ + enums.ChatMemberStatus.ADMINISTRATOR, + enums.ChatMemberStatus.OWNER, + ] + and message.from_user.id != 2024984460 + ): + return await message.reply_text("🦉🦉🦉") + await message.reply_to_message.copy(idtujuan, caption_entities=message.reply_to_message.entities, reply_markup=message.reply_to_message.reply_markup) + return await message.reply_text("Pesan berhasil dikirim..") + except UserNotParticipant: + return await message.reply("Command ini hanya untuk admin YMoviezNew") + except Exception as e: + return await message.reply(f"ERROR: {e}") + else: + await message.reply("Silahkan balas pesan yang mau dicopy.") + + +@app.on_message(filters.command(["forward"], COMMAND_HANDLER)) +@capture_err +async def forward(client, message): + if len(message.command) == 1: + if not message.reply_to_message: + return await message.reply("Silahkan balas pesan yang mau dicopy.") + try: + await message.reply_to_message.forward(message.from_user.id) + return await message.reply_text("Pesan berhasil dikirim..") + except UserIsBlocked: + return await message.reply("Silahkan PM Saya untuk memforward pesan ke chat pribadi..", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="💬 Chat Aku Yahh", url="https://t.me/MissKatyRoBot")]])) + except Exception as e: + return await message.reply(f"ERROR: {str(e)}") + elif message.reply_to_message: + try: + idtujuan = message.command[1] + userstat = await app.get_chat_member(-1001686184174, message.from_user.id) + if ( + userstat.status + not in [ + enums.ChatMemberStatus.ADMINISTRATOR, + enums.ChatMemberStatus.OWNER, + ] + and message.from_user.id != 2024984460 + ): + return await message.reply_text("🦉🦉🦉") + await message.reply_to_message.forward(idtujuan) + return await message.reply_text("Pesan berhasil dikirim..") + except UserNotParticipant: + return await message.reply("Comman ini hanya untuk admin YMoviezNew") + except Exception as e: + return await message.reply(f"ERROR: {e}") + else: + await message.reply("Silahkan balas pesan yang mau diforward.") diff --git a/misskaty/plugins/detect_afk.py b/misskaty/plugins/detect_afk.py new file mode 100644 index 00000000..151abc9f --- /dev/null +++ b/misskaty/plugins/detect_afk.py @@ -0,0 +1,220 @@ +import re +import time +import asyncio +from misskaty import app +from pyrogram import filters, enums +from database.afk_db import remove_afk, is_afk +from misskaty.helper.human_read import get_readable_time2 + +# Detect user that AFK based on Yukki Repo +@app.on_message( + filters.group & ~filters.bot & ~filters.via_bot, + group=1, +) +async def chat_watcher_func(_, message): + if message.sender_chat: + return + userid = message.from_user.id + user_name = message.from_user.first_name + if message.entities: + possible = ["/afk", f"/afk@MissKatyRoBot", "!afk"] + message_text = message.text or message.caption + for entity in message.entities: + if entity.type == enums.MessageEntityType.BOT_COMMAND: + if (message_text[0 : 0 + entity.length]).lower() in possible: + return + + msg = "" + replied_user_id = 0 + + # Self AFK + verifier, reasondb = await is_afk(userid) + if verifier: + await remove_afk(userid) + try: + afktype = reasondb["type"] + timeafk = reasondb["time"] + data = reasondb["data"] + reasonafk = reasondb["reason"] + seenago = get_readable_time2((int(time.time() - timeafk))) + if afktype == "text": + msg += f"**{user_name[:25]}** is back online and was away for {seenago}\n\n" + if afktype == "text_reason": + msg += f"**{user_name[:25]}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}\n\n" + if afktype == "animation": + if str(reasonafk) == "None": + await message.reply_animation( + data, + caption=f"**{user_name[:25]}** is back online and was away for {seenago}\n\n", + ) + else: + await message.reply_animation( + data, + caption=f"**{user_name[:25]}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}\n\n", + ) + if afktype == "photo": + if str(reasonafk) == "None": + await message.reply_photo( + photo=f"downloads/{userid}.jpg", + caption=f"**{user_name[:25]}** is back online and was away for {seenago}\n\n", + ) + else: + await message.reply_photo( + photo=f"downloads/{userid}.jpg", + caption=f"**{user_name[:25]}** is back online and was away for {seenago}\n\n**Reason:** {reasonafk}\n\n", + ) + except: + msg += f"**{user_name[:25]}** is back online.\n\n" + + # Replied to a User which is AFK + if message.reply_to_message: + try: + replied_first_name = message.reply_to_message.from_user.first_name + replied_user_id = message.reply_to_message.from_user.id + verifier, reasondb = await is_afk(replied_user_id) + if verifier: + try: + afktype = reasondb["type"] + timeafk = reasondb["time"] + data = reasondb["data"] + reasonafk = reasondb["reason"] + seenago = get_readable_time2((int(time.time() - timeafk))) + if afktype == "text": + msg += f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n" + if afktype == "text_reason": + msg += f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n" + if afktype == "animation": + if str(reasonafk) == "None": + await message.reply_animation( + data, + caption=f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_animation( + data, + caption=f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n", + ) + if afktype == "photo": + if str(reasonafk) == "None": + await message.reply_photo( + photo=f"downloads/{replied_user_id}.jpg", + caption=f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_photo( + photo=f"downloads/{replied_user_id}.jpg", + caption=f"**{replied_first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n", + ) + except Exception: + msg += f"**{replied_first_name}** is AFK\n\n" + except: + pass + + # If username or mentioned user is AFK + if message.entities: + entity = message.entities + j = 0 + for x in range(len(entity)): + if (entity[j].type) == enums.MessageEntityType.MENTION: + found = re.findall("@([_0-9a-zA-Z]+)", message.text) + try: + get_user = found[j] + user = await app.get_users(get_user) + if user.id == replied_user_id: + j += 1 + continue + except: + j += 1 + continue + verifier, reasondb = await is_afk(user.id) + if verifier: + try: + afktype = reasondb["type"] + timeafk = reasondb["time"] + data = reasondb["data"] + reasonafk = reasondb["reason"] + seenago = get_readable_time2((int(time.time() - timeafk))) + if afktype == "text": + msg += f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n" + if afktype == "text_reason": + msg += f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n" + if afktype == "animation": + if str(reasonafk) == "None": + await message.reply_animation( + data, + caption=f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_animation( + data, + caption=f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason**: {reasonafk}\n\n", + ) + if afktype == "photo": + if str(reasonafk) == "None": + await message.reply_photo( + photo=f"downloads/{user.id}.jpg", + caption=f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_photo( + photo=f"downloads/{user.id}.jpg", + caption=f"**{user.first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n", + ) + except: + msg += f"**{user.first_name[:25]}** is AFK\n\n" + elif (entity[j].type) == enums.MessageEntityType.TEXT_MENTION: + try: + user_id = entity[j].user.id + if user_id == replied_user_id: + j += 1 + continue + first_name = entity[j].user.first_name + except: + j += 1 + continue + verifier, reasondb = await is_afk(user_id) + if verifier: + try: + afktype = reasondb["type"] + timeafk = reasondb["time"] + data = reasondb["data"] + reasonafk = reasondb["reason"] + seenago = get_readable_time2((int(time.time() - timeafk))) + if afktype == "text": + msg += ( + f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n" + ) + if afktype == "text_reason": + msg += f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n" + if afktype == "animation": + if str(reasonafk) == "None": + await message.reply_animation( + data, + caption=f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_animation( + data, + caption=f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n", + ) + if afktype == "photo": + if str(reasonafk) == "None": + await message.reply_photo( + photo=f"downloads/{user_id}.jpg", + caption=f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n", + ) + else: + await message.reply_photo( + photo=f"downloads/{user_id}.jpg", + caption=f"**{first_name[:25]}** is AFK since {seenago} ago.\n\n**Reason:** {reasonafk}\n\n", + ) + except: + msg += f"**{first_name[:25]}** is AFK\n\n" + j += 1 + if msg != "": + try: + pesan = await message.reply_text(msg, disable_web_page_preview=True) + await asyncio.sleep(20) + await pesan.delete() + except: + return diff --git a/misskaty/plugins/dev.py b/misskaty/plugins/dev.py new file mode 100644 index 00000000..c7f5c245 --- /dev/null +++ b/misskaty/plugins/dev.py @@ -0,0 +1,144 @@ +import io +import sys +import os +import traceback +import asyncio +from pyrogram import filters, enums +from misskaty.vars import COMMAND_HANDLER, SUDO +from misskaty import app + +__MODULE__ = "DevCommand" +__HELP__ = """ +**For Owner Bot Only.** +/run [args] - Run eval CMD +/shell [args] - Run Exec/Terminal CMD +/download [link/reply_to_telegram_file] - Download file from Telegram + +**For Public Use** +/json - Send structure message Telegram in JSON using Pyrogram Style. +""" + + +@app.on_message(filters.command(["logs"]) & filters.user(SUDO)) +async def log_file(bot, message): + """Send log file""" + try: + await message.reply_document("MissKatyLogs.txt", caption="Log Bot MissKatyPyro") + except Exception as e: + await message.reply(str(e)) + + +@app.on_message(filters.command(["donate"], COMMAND_HANDLER)) +async def donate(_, message): + await message.reply_photo( + "AgACAgQAAxkBAAECsVNjbMvjxbN4gRafvNBH-Kv-Zqml8wACzq4xG95tbVPDeZ_UusonbAAIAQADAgADeQAHHgQ", + caption=f"Hai {message.from_user.mention}, jika kamu merasa bot ini berguna bisa melakukan donasi dengan scan kode QRIS diatas untuk kebutuhan server dan lainnya. Terimakasih..", + ) + + +@app.on_message( + filters.command(["balas"], COMMAND_HANDLER) & filters.user(SUDO) & filters.reply +) +async def balas(c, m): + pesan = m.text.split(" ", 1) + await m.delete() + await m.reply(pesan[1], reply_to_message_id=m.reply_to_message.id) + + +@app.on_message(filters.command(["neofetch"], COMMAND_HANDLER) & filters.user(SUDO)) +async def neofetch(c, m): + neofetch = (await shell_exec("neofetch --stdout"))[0] + await m.reply(f"{neofetch}") + + +@app.on_message(filters.command(["shell", "sh"], COMMAND_HANDLER) & filters.user(SUDO)) +@app.on_edited_message( + filters.command(["shell", "sh"], COMMAND_HANDLER) & filters.user(SUDO) +) +async def shell(client, message): + cmd = message.text.split(" ", 1) + if len(cmd) == 1: + return await message.reply("No command to execute was given.") + shell = (await shell_exec(cmd[1]))[0] + if len(shell) > 3000: + with open("shell_output.txt", "w") as file: + file.write(shell) + with open("shell_output.txt", "rb") as doc: + await message.reply_document(document=doc, file_name=doc.name) + try: + os.remove("shell_output.txt") + except: + pass + elif len(shell) != 0: + await message.reply(shell, parse_mode=enums.ParseMode.HTML) + else: + await message.reply("No Reply") + + +@app.on_message(filters.command(["ev", "run"]) & filters.user(SUDO)) +@app.on_edited_message(filters.command(["ev", "run"]) & filters.user(SUDO)) +async def evaluation_cmd_t(client, message): + status_message = await message.reply("__Processing eval pyrogram...__") + try: + cmd = message.text.split(" ", maxsplit=1)[1] + except IndexError: + return await status_message.edit("__No evaluate message!__") + 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(cmd, client, message) + 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 = f"**EVAL**:\n`{cmd}`\n\n**OUTPUT**:\n`{evaluation.strip()}`\n" + + if len(final_output) > 4096: + with open("MissKatyEval.txt", "w+", encoding="utf8") as out_file: + out_file.write(final_output) + await status_message.reply_document( + document="MissKatyEval.txt", + caption=cmd[: 4096 // 4 - 1], + disable_notification=True, + ) + os.remove("MissKatyEval.txt") + await status_message.delete() + else: + await status_message.edit(final_output, parse_mode=enums.ParseMode.MARKDOWN) + + +async def aexec(code, client, message): + exec( + "async def __aexec(client, message): " + + "".join(f"\n {l_}" for l_ in code.split("\n")) + ) + return await locals()["__aexec"](client, message) + + +async def shell_exec(code, treat=True): + process = await asyncio.create_subprocess_shell( + code, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT + ) + + stdout = (await process.communicate())[0] + if treat: + stdout = stdout.decode().strip() + return stdout, process diff --git a/misskaty/plugins/download_upload.py b/misskaty/plugins/download_upload.py new file mode 100644 index 00000000..bb7f3186 --- /dev/null +++ b/misskaty/plugins/download_upload.py @@ -0,0 +1,198 @@ +import time +import asyncio +import math +import os +import logging +import aiohttp +import json +from misskaty.helper.http import http +from bs4 import BeautifulSoup +from misskaty import app +from pySmartDL import SmartDL +from datetime import datetime +from misskaty.core.decorator.errors import capture_err +from misskaty.vars import COMMAND_HANDLER, SUDO +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from misskaty.helper.pyro_progress import ( + progress_for_pyrogram, + humanbytes, +) + +__MODULE__ = "Download/Upload" +__HELP__ = """ +/download [url] - Download file from URL (Sudo Only) +/download [reply_to_TG_File] - Download TG File +/tiktokdl [link] - Download TikTok Video +/fbdl [link] - Download Facebook Video +/anon [link] - Upload files to Anonfiles +/ytdown [link] - Download YouTube dengan YT-DLP +""" + + +@app.on_message(filters.command(["anon"], COMMAND_HANDLER)) +async def upload(bot, message): + if not message.reply_to_message: + return await message.reply("Please reply to media file.") + if message.reply_to_message is not None: + vid = [message.reply_to_message.video, message.reply_to_message.document] + for v in vid: + if v is not None: + break + m = await message.reply("Download your file to my Server...") + now = time.time() + fileku = await message.reply_to_message.download( + progress=progress_for_pyrogram, + progress_args=("Trying to download, please wait..", m, now), + ) + try: + files = {"file": open(fileku, "rb")} + await m.edit("Uploading to Anonfile, Please Wait||") + callapi = await http.post("https://api.anonfiles.com/upload", files=files) + text = callapi.json() + output = f'File Uploaded to Anonfile\n\n📂 File Name: {text["data"]["file"]["metadata"]["name"]}\n\n📦 File Size: {text["data"]["file"]["metadata"]["size"]["readable"]}\n\n📥 Download Link: {text["data"]["file"]["url"]["full"]}' + + btn = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "📥 Download 📥", url=f"{text['data']['file']['url']['full']}" + ) + ] + ] + ) + await m.edit(output, reply_markup=btn) + except Exception as e: + await bot.send_message(message.chat.id, text=f"Something Went Wrong!\n\n{e}") + os.remove(sed) + + +@app.on_message(filters.command(["download"], COMMAND_HANDLER) & filters.user(SUDO)) +@capture_err +async def download(client, message): + pesan = await message.reply_text("Processing...", quote=True) + if message.reply_to_message is not None: + start_t = datetime.now() + c_time = time.time() + the_real_download_location = await client.download_media( + message=message.reply_to_message, + progress=progress_for_pyrogram, + progress_args=("trying to download, sabar yakk..", pesan, c_time), + ) + end_t = datetime.now() + ms = (end_t - start_t).seconds + await pesan.edit( + f"Downloaded to {the_real_download_location} in {ms} seconds." + ) + elif len(message.command) > 1: + start_t = datetime.now() + the_url_parts = " ".join(message.command[1:]) + url = the_url_parts.strip() + custom_file_name = os.path.basename(url) + if "|" in the_url_parts: + url, custom_file_name = the_url_parts.split("|") + url = url.strip() + custom_file_name = custom_file_name.strip() + download_file_path = os.path.join("downloads/", custom_file_name) + downloader = SmartDL(url, download_file_path, progress_bar=False) + downloader.start(blocking=False) + c_time = time.time() + while not downloader.isFinished(): + total_length = downloader.filesize or None + downloaded = downloader.get_dl_size() + display_message = "" + now = time.time() + diff = now - c_time + percentage = downloader.get_progress() * 100 + speed = downloader.get_speed() + round(diff) * 1000 + progress_str = "[{0}{1}]\nProgress: {2}%".format( + "".join(["█" for _ in range(math.floor(percentage / 5))]), + "".join(["░" for _ in range(20 - math.floor(percentage / 5))]), + round(percentage, 2), + ) + + estimated_total_time = downloader.get_eta(human=True) + try: + current_message = "trying to download...\n" + current_message += f"URL: {url}\n" + current_message += f"File Name: {custom_file_name}\n" + current_message += f"Speed: {speed}\n" + current_message += f"{progress_str}\n" + current_message += ( + f"{humanbytes(downloaded)} of {humanbytes(total_length)}\n" + ) + current_message += f"ETA: {estimated_total_time}" + if round(diff % 10.00) == 0 and current_message != display_message: + await pesan.edit( + disable_web_page_preview=True, text=current_message + ) + display_message = current_message + await asyncio.sleep(10) + except Exception as e: + logging.info(str(e)) + if os.path.exists(download_file_path): + end_t = datetime.now() + ms = (end_t - start_t).seconds + await pesan.edit( + f"Downloaded to {download_file_path} in {ms} seconds" + ) + else: + await pesan.edit( + "Reply to a Telegram Media, to download it to my local server." + ) + + +@app.on_message(filters.command(["tiktokdl"], COMMAND_HANDLER)) +@capture_err +async def tiktokdl(client, message): + if len(message.command) == 1: + return await message.reply( + f"Use command /{message.command[0]} [link] to download tiktok video." + ) + link = message.command[1] + msg = await message.reply("Trying download...") + try: + r = (await http.get(f"https://api.hayo.my.id/api/tiktok/4?url={link}")).json() + await message.reply_video( + r["linkori"], + caption=f"Title: {r['name']}\n\nUploaded for {message.from_user.mention} [{message.from_user.id}]", + ) + await msg.delete() + except Exception as e: + await message.reply(f"Failed to download tiktok video..\n\nReason: {e}") + await msg.delete() + + +@app.on_message(filters.command(["fbdl"], COMMAND_HANDLER)) +@capture_err +async def fbdl(client, message): + if len(message.command) == 1: + return await message.reply( + f"Use command /{message.command[0]} [link] to download Facebook video." + ) + link = message.command[1] + msg = await message.reply("Trying download...") + try: + resjson = (await http.get(f"https://yasirapi.eu.org/fbdl?link={link}")).json() + try: + url = resjson["result"]["links"]["hd"].replace("&", "&") + except: + url = resjson["result"]["links"]["sd"].replace("&", "&") + obj = SmartDL(url, progress_bar=False) + obj.start() + path = obj.get_dest() + await message.reply_video( + path, + caption=f"{os.path.basename(path)}\n\nUploaded for {message.from_user.mention} [{message.from_user.id}]", + ) + await msg.delete() + try: + os.remove(path) + except: + pass + except Exception as e: + await message.reply( + f"Failed to download Facebook video..\n\nReason: {e}" + ) + await msg.delete() diff --git a/misskaty/plugins/filter_request.py b/misskaty/plugins/filter_request.py new file mode 100644 index 00000000..656ac802 --- /dev/null +++ b/misskaty/plugins/filter_request.py @@ -0,0 +1,199 @@ +import re +import random +from misskaty import app +from pyrogram import enums, filters +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram.errors import UserNotParticipant, PeerIdInvalid +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.time_gap import check_time_gap + +chat = [-1001128045651, -1001255283935, -1001455886928] +REQUEST_DB = {} + + +@app.on_message(filters.regex(r"alamu'?ala[iy]ku+m", re.I) & filters.chat(chat)) +async def start(_, message): + await message.reply_text(text=f"Wa'alaikumsalam {message.from_user.mention} 😇") + + +@app.on_message(filters.regex(r"#request|#req", re.I) & (filters.text | filters.photo) & filters.chat(-1001255283935) & ~filters.channel) +@capture_err +async def request_user(client, message): + if message.sender_chat: + return await message.reply(f"{message.from_user.mention} mohon gunakan akun asli saat request.") + is_in_gap, sleep_time = await check_time_gap(message.from_user.id) + if is_in_gap: + return await message.reply("Sabar dikit napa.. 🙄") + markup = InlineKeyboardMarkup( + [ + [InlineKeyboardButton(text="💬 Lihat Pesan", url=f"https://t.me/c/1255283935/{message.id}")], + [InlineKeyboardButton(text="🚫 Tolak", callback_data=f"rejectreq_{message.id}_{message.chat.id}"), InlineKeyboardButton(text="✅ Done", callback_data=f"donereq_{message.id}_{message.chat.id}")], + [InlineKeyboardButton(text="⚠️ Tidak Tersedia", callback_data=f"unavailablereq_{message.id}_{message.chat.id}")], + [InlineKeyboardButton(text="🔍 Sudah Ada", callback_data=f"dahada_{message.id}_{message.chat.id}")], + ] + ) + try: + user_id = message.from_user.id + if user_id in REQUEST_DB: + REQUEST_DB[user_id] += 1 + else: + REQUEST_DB[user_id] = 1 + if REQUEST_DB[user_id] > 3: + return await message.reply(f"Mohon maaf {message.from_user.mention}, maksimal request hanya 3x perhari. Kalo mau tambah 5k per request 😝😝.") + if message.text: + forward = await client.send_message(-1001575525902, f"Request by {message.from_user.first_name} (#id{message.from_user.id})\n\n{message.text}", reply_markup=markup) + markup2 = InlineKeyboardMarkup([[InlineKeyboardButton(text="⏳ Cek Status Request", url=f"https://t.me/c/1575525902/{forward.id}")]]) + if message.photo: + forward = await client.send_photo( + -1001575525902, message.photo.file_id, caption=f"Request by {message.from_user.first_name} (#id{message.from_user.id})\n\n{message.caption}", reply_markup=markup + ) + markup2 = InlineKeyboardMarkup([[InlineKeyboardButton(text="⏳ Cek Status Request", url=f"https://t.me/c/1575525902/{forward.id}")]]) + await message.reply_text(text=f"Hai {message.from_user.mention}, request kamu sudah dikirim yaa. Harap bersabar mungkin admin juga punya kesibukan lain.\n\nSisa Request: {3 - REQUEST_DB[user_id]}x", quote=True, reply_markup=markup2) + except: + pass + + +async def clear_reqdict(): + REQUEST_DB.clear() + + +# @app.on_message(filters.regex(r"makasi|thank|terimakasih|terima kasih|mksh", re.I) & filters.chat(chat)) +async def start(_, message): + pesan = [ + f"Sama-sama {message.from_user.first_name}", + f"You're Welcome {message.from_user.first_name}", + "Oke..", + "Yoi..", + "Terimakasih Kembali..", + "Sami-Sami...", + "Sama-sama, senang bisa membantu..", + f"Yups, Sama-sama {message.from_user.first_name}", + "Okayyy...", + ] + await message.reply_text(text=random.choice(pesan)) + + +@app.on_callback_query(filters.regex(r"^donereq")) +async def _callbackreq(c, q): + try: + user = await c.get_chat_member(-1001201566570, q.from_user.id) + if user.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]: + i, msg_id, chat_id = q.data.split("_") + await c.send_message( + chat_id=chat_id, + text="#Done\nDone ✅, Selamat menonton. Jika request tidak bisa dilihat digrup silahkan join channel melalui link private yang ada di @YMovieZ_New ...", + reply_to_message_id=int(msg_id), + ) + + if q.message.caption: + await q.message.edit_text(f"COMPLETED\n\n{q.message.caption}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="✅ Request Completed", callback_data="reqcompl")]])) + else: + await q.message.edit_text(f"COMPLETED\n\n{q.message.text}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="✅ Request Completed", callback_data="reqcompl")]])) + await q.answer("Request berhasil diselesaikan ✅") + else: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True) + except UserNotParticipant: + return await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True, cache_time=10) + except PeerIdInvalid: + return await q.answer("Silahkan kirim pesan digrup supaya bot bisa merespon.", show_alert=True, cache_time=10) + + +@app.on_callback_query(filters.regex(r"^dahada")) +async def _callbackreqada(c, q): + try: + user = await c.get_chat_member(-1001201566570, q.from_user.id) + if user.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]: + i, msg_id, chat_id = q.data.split("_") + await c.send_message( + chat_id=chat_id, + text="#SudahAda\nFilm/series yang direquest sudah ada sebelumnya. Biasakan mencari terlebih dahulu..", + reply_to_message_id=int(msg_id), + ) + + if q.message.caption: + await q.message.edit_text(f"#AlreadyAvailable\n\n{q.message.caption}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="🔍 Request Sudah Ada", callback_data="reqavailable")]])) + else: + await q.message.edit_text(f"Already Available\n\n{q.message.text}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="🔍 Request Sudah Ada", callback_data="reqavailable")]])) + await q.answer("Done ✔️") + else: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True) + except UserNotParticipant: + return await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True, cache_time=10) + except PeerIdInvalid: + return await q.answer("Silahkan kirim pesan digrup supaya bot bisa merespon.", show_alert=True, cache_time=10) + + +@app.on_callback_query(filters.regex(r"^rejectreq")) +async def _callbackreject(c, q): + try: + user = await c.get_chat_member(-1001201566570, q.from_user.id) + if user.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]: + i, msg_id, chat_id = q.data.split("_") + await c.send_message( + chat_id=chat_id, + text="Mohon maaf, request kamu ditolak karena tidak sesuai rules. Harap baca rules grup no.6 yaa 🙃.", + reply_to_message_id=int(msg_id), + ) + + if q.message.caption: + await q.message.edit_text(f"REJECTED\n\n{q.message.caption}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="🚫 Request Rejected", callback_data="reqreject")]])) + else: + await q.message.edit_text(f"REJECTED\n\n{q.message.text}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="🚫 Request Rejected", callback_data="reqreject")]])) + await q.answer("Request berhasil ditolak 🚫") + else: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True) + except UserNotParticipant: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True, cache_time=10) + except PeerIdInvalid: + return await q.answer("Silahkan kirim pesan digrup supaya bot bisa merespon.", show_alert=True, cache_time=10) + + +@app.on_callback_query(filters.regex(r"^unavailablereq")) +async def _callbackunav(c, q): + try: + user = await c.get_chat_member(-1001201566570, q.from_user.id) + if user.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]: + i, msg_id, chat_id = q.data.split("_") + await c.send_message( + chat_id=chat_id, + text="Mohon maaf, request kamu tidak tersedia. Silahkan baca beberapa alasannya di channel @YMovieZ_New", + reply_to_message_id=int(msg_id), + ) + + if q.message.caption: + await q.message.edit_text(f"UNAVAILABLE\n\n{q.message.caption}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="⚠️ Request Unavailable", callback_data="requnav")]])) + else: + await q.message.edit_text(f"UNAVAILABLE\n\n{q.message.text}", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="⚠️ Request Unavailable", callback_data="requnav")]])) + await q.answer("Request tidak tersedia, mungkin belum rilis atau memang tidak tersedia versi digital.") + else: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True, cache_time=1000) + except UserNotParticipant: + await q.answer("Apa motivasi kamu menekan tombol ini?", show_alert=True, cache_time=10) + except PeerIdInvalid: + return await q.answer("Silahkan kirim pesan digrup supaya bot bisa merespon.", show_alert=True, cache_time=10) + + +@app.on_callback_query(filters.regex(r"^reqcompl$")) +async def _callbackaft_done(c, q): + await q.answer("Request ini sudah terselesaikan 🥳, silahkan cek di channel atau grup yaa..", show_alert=True, cache_time=1000) + + +@app.on_callback_query(filters.regex(r"^reqreject$")) +async def _callbackaft_rej(c, q): + await q.answer("Request ini ditolak 💔, silahkan cek rules grup yaa.", show_alert=True, cache_time=1000) + + +@app.on_callback_query(filters.regex(r"^requnav$")) +async def _callbackaft_unav(c, q): + await q.answer("Request ini tidak tersedia ☹️, mungkin filmnya belum rilis atau memang tidak tersedia versi digital.", show_alert=True, cache_time=1000) + + +@app.on_callback_query(filters.regex(r"^reqavailable$")) +async def _callbackaft_dahada(c, q): + await q.answer("Request ini sudah ada, silahkan cari 🔍 di channelnya yaa 😉..", show_alert=True) + + +scheduler = AsyncIOScheduler(timezone="Asia/Jakarta") +scheduler.add_job(clear_reqdict, trigger="cron", hour=7, minute=0) +scheduler.start() diff --git a/misskaty/plugins/genss.py b/misskaty/plugins/genss.py new file mode 100644 index 00000000..5fed89f0 --- /dev/null +++ b/misskaty/plugins/genss.py @@ -0,0 +1,142 @@ +# the logging things +import logging + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) + +import os, time, traceback +from asyncio import sleep, gather +from shutil import rmtree +from pyrogram import filters, enums +from pyrogram.errors import FloodWait +from misskaty import app +from misskaty.helper.ffmpeg_helper import take_ss, genss_link +from misskaty.vars import COMMAND_HANDLER +from misskaty.helper.pyro_progress import progress_for_pyrogram + +__MODULE__ = "MediaTool" +__HELP__ = """" +/genss [reply to video] - Generate Screenshot From Video. +/genss_link [link] - Generate Screenshot Video From URL. (Unstable) +/mediainfo [link/reply to TG Video] - Get Mediainfo From File. +""" + + +@app.on_message(filters.command(["genss"], COMMAND_HANDLER)) +async def genss(client, message): + if message.reply_to_message is not None: + process = await message.reply_text("`Processing, please wait..`") + c_time = time.time() + the_real_download_location = await client.download_media( + message=message.reply_to_message, + progress=progress_for_pyrogram, + progress_args=("Trying to download, please wait..", process, c_time), + ) + if the_real_download_location is not None: + try: + await client.edit_message_text( + text=f"File video berhasil didownload dengan path {the_real_download_location}.", + chat_id=message.chat.id, + message_id=process.id, + ) + await sleep(2) + images = await take_ss(the_real_download_location) + await client.edit_message_text( + text="Mencoba mengupload, hasil generate screenshot..", + chat_id=message.chat.id, + message_id=process.id, + ) + await client.send_chat_action( + chat_id=message.chat.id, action=enums.ChatAction.UPLOAD_PHOTO + ) + + try: + await gather( + *[ + message.reply_document( + images, reply_to_message_id=message.id + ), + message.reply_photo(images, reply_to_message_id=message.id), + ] + ) + except FloodWait as e: + await sleep(e.value) + await gather( + *[ + message.reply_document( + images, reply_to_message_id=message.id + ), + message.reply_photo(images, reply_to_message_id=message.id), + ] + ) + await message.reply( + f"☑️ Uploaded [1] screenshoot.\n\n{message.from_user.first_name} ({message.from_user.id})\n#️⃣ #ssgen #id{message.from_user.id}\n\nSS Generate by @MissKatyRoBot", + reply_to_message_id=message.id, + ) + await process.delete() + try: + os.remove(images) + os.remove(the_real_download_location) + except: + pass + except Exception: + exc = traceback.format_exc() + await message.reply(f"Gagal generate screenshot.\n\n{exc}") + try: + os.remove(images) + os.remove(the_real_download_location) + except: + pass + else: + await message.reply("Reply to a Telegram media to get screenshots..") + + +@app.on_message(filters.command(["genss_link"], COMMAND_HANDLER)) +async def genss_link(client, message): + try: + link = message.text.split(" ")[1] + if link.startswith("https://file.yasirweb.my.id"): + link = link.replace( + "https://file.yasirweb.my.id", "https://file.yasiraris.workers.dev" + ) + if link.startswith("https://link.yasirweb.my.id"): + link = link.replace( + "https://link.yasirweb.my.id", "https://yasirrobot.herokuapp.com" + ) + process = await message.reply_text("`Processing, please wait..`") + tmp_directory_for_each_user = f"./MissKaty_Genss/{str(message.from_user.id)}" + if not os.path.isdir(tmp_directory_for_each_user): + os.makedirs(tmp_directory_for_each_user) + images = await genss_link(process, link, tmp_directory_for_each_user, 5, 8) + await sleep(2) + await client.edit_message_text( + text="Mencoba mengupload, hasil generate screenshot..", + chat_id=message.chat.id, + message_id=process.id, + ) + await client.send_chat_action( + chat_id=message.chat.id, action=enums.ChatAction.UPLOAD_PHOTO + ) + try: + await message.reply_media_group(images, reply_to_message_id=message.id) + except FloodWait as e: + await sleep(e.value) + await message.reply_media_group(images, reply_to_message_id=message.id) + await message.reply( + f"☑️ Uploaded [8] screenshoot.\n\nGenerated by @MissKatyRoBot.", + reply_to_message_id=message.id, + ) + await process.delete() + try: + rmtree(tmp_directory_for_each_user) + except: + pass + except Exception: + exc = traceback.format_exc() + await message.reply(f"Gagal generate screenshot.\n\n{exc}") + try: + rmtree(tmp_directory_for_each_user) + except: + pass diff --git a/misskaty/plugins/grup_tools.py b/misskaty/plugins/grup_tools.py new file mode 100644 index 00000000..b0a9c028 --- /dev/null +++ b/misskaty/plugins/grup_tools.py @@ -0,0 +1,347 @@ +from datetime import datetime, timedelta +import time +import os +import logging +from misskaty.helper.http import http +from pyrogram import enums, filters +from pyrogram.types import ChatMemberUpdated, InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram.errors import ChatSendMediaForbidden, MessageTooLong, RPCError, SlowmodeWait +from misskaty import app +from misskaty.core.decorator.errors import capture_err, asyncify +from PIL import Image, ImageChops, ImageDraw, ImageFont +import textwrap +from database.users_chats_db import db +from utils import temp +from pyrogram.errors import ChatAdminRequired +from misskaty.vars import SUDO, LOG_CHANNEL, SUPPORT_CHAT, COMMAND_HANDLER + +logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") +LOGGER = logging.getLogger(__name__) + + +def circle(pfp, size=(215, 215)): + pfp = pfp.resize(size, Image.ANTIALIAS).convert("RGBA") + bigsize = (pfp.size[0] * 3, pfp.size[1] * 3) + mask = Image.new("L", bigsize, 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0) + bigsize, fill=255) + mask = mask.resize(pfp.size, Image.ANTIALIAS) + mask = ImageChops.darker(mask, pfp.split()[-1]) + pfp.putalpha(mask) + return pfp + + +def draw_multiple_line_text(image, text, font, text_start_height): + """ + From unutbu on [python PIL draw multiline text on image](https://stackoverflow.com/a/7698300/395857) + """ + draw = ImageDraw.Draw(image) + image_width, image_height = image.size + y_text = text_start_height + lines = textwrap.wrap(text, width=50) + for line in lines: + line_width, line_height = font.getsize(line) + draw.text(((image_width - line_width) / 2, y_text), line, font=font, fill="black") + y_text += line_height + + +@asyncify +def welcomepic(pic, user, chat, count, id): + background = Image.open("img/bg.png") # <- Background Image (Should be PNG) + background = background.resize((1024, 500), Image.ANTIALIAS) + pfp = Image.open(pic).convert("RGBA") + pfp = circle(pfp) + pfp = pfp.resize((265, 265)) # Resizes the Profilepicture so it fits perfectly in the circle + font = ImageFont.truetype("Calistoga-Regular.ttf", 37) # <- Text Font of the Member Count. Change the text size for your preference + member_text = f"User#{count}, Selamat Datang {user}" # <- Text under the Profilepicture with the Membercount + draw_multiple_line_text(background, member_text, font, 395) + draw_multiple_line_text(background, chat, font, 47) + ImageDraw.Draw(background).text((530, 460), "Generated by @MissKatyRoBot", font=ImageFont.truetype("Calistoga-Regular.ttf", 28), size=20, align="right") + background.paste(pfp, (379, 123), pfp) # Pastes the Profilepicture on the Background Image + background.save(f"downloads/welcome#{id}.png") # Saves the finished Image in the folder with the filename + return f"downloads/welcome#{id}.png" + + +@app.on_chat_member_updated(filters.group & filters.chat(-1001128045651)) +async def member_has_joined(c: app, member: ChatMemberUpdated): + if not member.new_chat_member or member.new_chat_member.status in {"banned", "left", "restricted"} or member.old_chat_member: + return + user = member.new_chat_member.user if member.new_chat_member else member.from_user + if user.id in SUDO: + await c.send_message( + member.chat.id, + "Waw, owner ku yang keren baru saja bergabung ke grup!", + ) + return + elif user.is_bot: + return # ignore bots + else: + if (temp.MELCOW).get(f"welcome-{member.chat.id}") is not None: + try: + await (temp.MELCOW[f"welcome-{member.chat.id}"]).delete() + except: + pass + mention = f"{user.first_name}" + joined_date = datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d %H:%M:%S") + first_name = f"{user.first_name} {user.last_name}" if user.last_name else user.first_name + id = user.id + dc = user.dc_id or "Member tanpa PP" + count = await app.get_chat_members_count(member.chat.id) + try: + pic = await app.download_media(user.photo.big_file_id, file_name=f"pp{user.id}.png") + except AttributeError: + pic = "img/profilepic.png" + welcomeimg = await welcomepic(pic, user.first_name, member.chat.title, count, user.id) + temp.MELCOW[f"welcome-{member.chat.id}"] = await c.send_photo( + member.chat.id, + photo=welcomeimg, + caption=f"Hai {mention}, Selamat datang digrup {member.chat.title} harap baca rules di pinned message terlebih dahulu.\n\nNama : {first_name}\nID : {id}\nDC ID : {dc}\nTanggal Join : {joined_date}", + ) + userspammer = "" + # Spamwatch Detection + try: + headers = {"Authorization": "Bearer XvfzE4AUNXkzCy0DnIVpFDlxZi79lt6EnwKgBj8Quuzms0OSdHvf1k6zSeyzZ_lz"} + apispamwatch = (await http.get(f"https://api.spamwat.ch/banlist/{user.id}", headers=headers)).json() + if not apispamwatch.get("error"): + await app.ban_chat_member(member.chat.id, user.id, datetime.now() + timedelta(seconds=30)) + userspammer += f"#SpamWatch Federation Ban\nUser {mention} [{user.id}] has been kicked because {apispamwatch.get('reason')}.\n" + except Exception as err: + LOGGER.error(f"ERROR in Spamwatch Detection. {err}") + # Combot API Detection + try: + apicombot = (await http.get(f"https://api.cas.chat/check?user_id={user.id}")).json() + if apicombot.get("ok") == "true": + await app.ban_chat_member(member.chat.id, user.id, datetime.now() + timedelta(seconds=30)) + userspammer += f"#CAS Federation Ban\nUser {mention} [{user.id}] detected as spambot and has been kicked. Powered by Combot AntiSpam." + except Exception as err: + LOGGER.error(f"ERROR in Combot API Detection. {err}") + if userspammer != "": + await c.send_message(member.chat.id, userspammer) + try: + os.remove(f"downloads/welcome#{user.id}.png") + os.remove(f"downloads/pp{user.id}.png") + except Exception: + pass + + +@app.on_message(filters.new_chat_members & filters.group) +async def save_group(bot, message): + r_j_check = [u.id for u in message.new_chat_members] + if temp.ME in r_j_check: + if not await db.get_chat(message.chat.id): + total = await bot.get_chat_members_count(message.chat.id) + r_j = message.from_user.mention if message.from_user else "Anonymous" + await bot.send_message( + LOG_CHANNEL, + f"#NewGroup\nGroup = {message.chat.title}({message.chat.id})\nMembers Count = {total}\nAdded by - {r_j}", + ) + + await db.add_chat(message.chat.id, message.chat.title) + if message.chat.id in temp.BANNED_CHATS: + # Inspired from a boat of a banana tree + buttons = [[InlineKeyboardButton("Support", url=f"https://t.me/{SUPPORT_CHAT}")]] + reply_markup = InlineKeyboardMarkup(buttons) + k = await message.reply( + text="CHAT NOT ALLOWED 🐞\n\nMy admins has restricted me from working here ! If you want to know more about it contact support..", + reply_markup=reply_markup, + ) + + try: + await k.pin() + except: + pass + await bot.leave_chat(message.chat.id) + return + buttons = [[InlineKeyboardButton("ℹ️ Help", url=f"https://t.me/{temp.U_NAME}?start=help"), InlineKeyboardButton("📢 Updates", url="https://t.me/YasirPediaChannel")]] + reply_markup = InlineKeyboardMarkup(buttons) + await message.reply_text(text=f"Terimakasih sudah menambahkan saya di {message.chat.title} ❣️\n\nJika ada kendala atau saran bisa kontak ke saya.", reply_markup=reply_markup) + else: + for u in message.new_chat_members: + count = await app.get_chat_members_count(message.chat.id) + try: + pic = await app.download_media(u.photo.big_file_id, file_name=f"pp{u.id}.png") + except AttributeError: + pic = "img/profilepic.png" + welcomeimg = await welcomepic(pic, u.first_name, message.chat.title, count, u.id) + if (temp.MELCOW).get(f"welcome-{message.chat.id}") is not None: + try: + await (temp.MELCOW[f"welcome-{message.chat.id}"]).delete() + except: + pass + try: + temp.MELCOW[f"welcome-{message.chat.id}"] = await app.send_photo( + message.chat.id, + photo=welcomeimg, + caption=f"Hai {u.mention}, Selamat datang digrup {message.chat.title}.", + ) + except (ChatSendMediaForbidden, SlowmodeWait): + await app.leave_chat(message.chat.id) + try: + os.remove(f"downloads/welcome#{u.id}.png") + os.remove(f"downloads/pp{u.id}.png") + except Exception: + pass + + +@app.on_message(filters.command("leave") & filters.user(SUDO)) +async def leave_a_chat(bot, message): + if len(message.command) == 1: + return await message.reply("Give me a chat id") + chat = message.command[1] + try: + chat = int(chat) + except: + chat = chat + try: + buttons = [[InlineKeyboardButton("Support", url=f"https://t.me/{SUPPORT_CHAT}")]] + reply_markup = InlineKeyboardMarkup(buttons) + await bot.send_message( + chat_id=chat, + text="Hai kawan, \nOwner aku bilang saya harus pergi! Jika kamu ingin menambahkan bot ini lagi silahkan kontak owner bot ini.", + reply_markup=reply_markup, + ) + await bot.leave_chat(chat) + except Exception as e: + await message.reply(f"Error - {e}") + await bot.leave_chat(chat) + + +@app.on_message(filters.command("disable") & filters.user(SUDO)) +async def disable_chat(bot, message): + if len(message.command) == 1: + return await message.reply("Give me a chat id") + r = message.text.split(None) + if len(r) > 2: + reason = message.text.split(None, 2)[2] + chat = message.text.split(None, 2)[1] + else: + chat = message.command[1] + reason = "No reason Provided" + try: + chat_ = int(chat) + except: + return await message.reply("Give Me A Valid Chat ID") + cha_t = await db.get_chat(chat_) + if not cha_t: + return await message.reply("Chat Not Found In DB") + if cha_t["is_disabled"]: + return await message.reply(f"This chat is already disabled:\nReason- {cha_t['reason']} ") + await db.disable_chat(chat_, reason) + temp.BANNED_CHATS.append(chat_) + await message.reply("Chat Succesfully Disabled") + try: + buttons = [[InlineKeyboardButton("Support", url=f"https://t.me/{SUPPORT_CHAT}")]] + reply_markup = InlineKeyboardMarkup(buttons) + await bot.send_message(chat_id=chat_, text=f"Hello Friends, \nMy admin has told me to leave from group so i go! If you wanna add me again contact my support group. \nReason : {reason}", reply_markup=reply_markup) + await bot.leave_chat(chat_) + except Exception as e: + await message.reply(f"Error - {e}") + + +@app.on_message(filters.command("enable") & filters.user(SUDO)) +async def re_enable_chat(bot, message): + if len(message.command) == 1: + return await message.reply("Give me a chat id") + chat = message.command[1] + try: + chat_ = int(chat) + except: + return await message.reply("Give Me A Valid Chat ID") + sts = await db.get_chat(int(chat)) + if not sts: + return await message.reply("Chat Not Found In DB !") + if not sts.get("is_disabled"): + return await message.reply("This chat is not yet disabled.") + await db.re_enable_chat(chat_) + temp.BANNED_CHATS.remove(chat_) + await message.reply("Chat Succesfully re-enabled") + + +# a function for trespassing into others groups, Inspired by a Vazha +# Not to be used , But Just to showcase his vazhatharam. +# @app.on_message(filters.command('invite') & filters.user(SUDO)) +async def gen_invite(bot, message): + if len(message.command) == 1: + return await message.reply("Give me a chat id") + chat = message.command[1] + try: + chat = int(chat) + except: + return await message.reply("Give Me A Valid Chat ID") + try: + link = await bot.create_chat_invite_link(chat) + except ChatAdminRequired: + return await message.reply("Invite Link Generation Failed, Iam Not Having Sufficient Rights") + except Exception as e: + return await message.reply(f"Error {e}") + await message.reply(f"Here is your Invite Link {link.invite_link}") + + +@app.on_message(filters.command(["adminlist", "adminlist@MissKatyRoBot"], COMMAND_HANDLER)) +@capture_err +async def adminlist(_, message): + if message.chat.type == enums.ChatType.PRIVATE: + return await message.reply("Perintah ini hanya untuk grup") + try: + administrators = [] + async for m in app.get_chat_members(message.chat.id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + administrators.append(f"{m.user.first_name}") + + res = "".join(f"~ {i}\n" for i in administrators) + return await message.reply(f"Daftar Admin di {message.chat.title} ({message.chat.id}):\n~ {res}") + except Exception as e: + await message.reply(f"ERROR: {str(e)}") + + +@app.on_message(filters.command(["kickme"], COMMAND_HANDLER)) +@capture_err +async def kickme(_, message): + reason = None + if len(message.text.split()) >= 2: + reason = message.text.split(None, 1)[1] + try: + await message.ban_member(message.from_user.id) + txt = f"Pengguna {message.from_user.mention} menendang dirinya sendiri. Mungkin dia sedang frustasi 😕" + txt += f"\nAlasan: {reason}" if reason else "" + await message.reply_text(txt) + await message.unban_member(message.from_user.id) + except RPCError as ef: + await message.reply_text(f"Sepertinya ada error, silahkan report ke owner saya. \nERROR: {str(ef)}") + return + + +@app.on_message(filters.command("users") & filters.user(SUDO)) +async def list_users(bot, message): + # https://t.me/GetTGLink/4184 + raju = await message.reply("Getting List Of Users") + users = await db.get_all_users() + out = "Users Saved In DB Are:\n\n" + async for user in users: + out += f"{user['name']}" + if user["ban_status"]["is_banned"]: + out += "( Banned User )" + out += "\n" + try: + await raju.edit_text(out) + except MessageTooLong: + with open("users.txt", "w+") as outfile: + outfile.write(out) + await message.reply_document("users.txt", caption="List Of Users") + + +@app.on_message(filters.command("chats") & filters.user(SUDO)) +async def list_chats(bot, message): + raju = await message.reply("Getting List Of chats") + chats = await db.get_all_chats() + out = "Chats Saved In DB Are:\n\n" + async for chat in chats: + out += f"**Title:** `{chat['title']}`\n**- ID:** `{chat['id']}`" + if chat["chat_status"]["is_disabled"]: + out += "( Disabled Chat )" + out += "\n" + try: + await raju.edit_text(out) + except MessageTooLong: + with open("chats.txt", "w+") as outfile: + outfile.write(out) + await message.reply_document("chats.txt", caption="List Of Chats") diff --git a/misskaty/plugins/inkick_user.py b/misskaty/plugins/inkick_user.py new file mode 100644 index 00000000..ace0aa7c --- /dev/null +++ b/misskaty/plugins/inkick_user.py @@ -0,0 +1,233 @@ +import time +from asyncio import sleep +from misskaty import app +from misskaty.vars import COMMAND_HANDLER +from pyrogram import enums, filters +from pyrogram.errors import FloodWait +from pyrogram.errors.exceptions.forbidden_403 import ChatWriteForbidden +from pyrogram.errors.exceptions.bad_request_400 import ( + ChatAdminRequired, + UserAdminInvalid, +) + +__MODULE__ = "Inkick" +__HELP__ = """" +/instatus - View member status in group. +/dkick - Remove deleted account from group. +""" + + +@app.on_message( + filters.incoming & ~filters.private & filters.command(["inkick"], COMMAND_HANDLER) +) +async def inkick(_, message): + user = await app.get_chat_member(message.chat.id, message.from_user.id) + if user.status.value in ("administrator", "owner"): + if len(message.command) > 1: + input_str = message.command + sent_message = await message.reply_text( + "🚮**Sedang membersihkan user, mungkin butuh waktu beberapa saat...**" + ) + count = 0 + async for member in app.get_chat_members(message.chat.id): + if member.user.is_bot: + continue + if ( + member.user.status.value in input_str + and member.status.value not in ("administrator", "owner") + ): + try: + await message.chat.ban_member(member.user.id) + count += 1 + await sleep(1) + await message.chat.unban_member(member.user.id) + except (ChatAdminRequired, UserAdminInvalid): + await sent_message.edit( + "❗**Oh tidaakk, saya bukan admin disini**\n__Saya pergi dari sini, tambahkan aku kembali dengan perijinan banned pengguna.__" + ) + await app.leave_chat(message.chat.id) + break + except FloodWait as e: + await sleep(e.value) + try: + await sent_message.edit( + f"✔️ **Berhasil menendang {count} pengguna berdasarkan argumen.**" + ) + + except ChatWriteForbidden: + await app.leave_chat(message.chat.id) + else: + await message.reply_text( + "❗ **Arguments Required**\n__See /help in personal message for more information.__" + ) + else: + sent_message = await message.reply_text( + "❗ **You have to be the group creator to do that.**" + ) + await sleep(5) + await sent_message.delete() + + +# Kick User Without Username +@app.on_message( + filters.incoming & ~filters.private & filters.command(["uname"], COMMAND_HANDLER) +) +async def uname(_, message): + user = await app.get_chat_member(message.chat.id, message.from_user.id) + if user.status.value in ("administrator", "owner"): + sent_message = await message.reply_text( + "🚮**Sedang membersihkan user, mungkin butuh waktu beberapa saat...**" + ) + count = 0 + async for member in app.get_chat_members(message.chat.id): + if not member.user.username and member.status.value not in ( + "administrator", + "owner", + ): + try: + await message.chat.ban_member(member.user.id) + count += 1 + await sleep(1) + await message.chat.unban_member(member.user.id) + except (ChatAdminRequired, UserAdminInvalid): + await sent_message.edit( + "❗**Oh tidaakk, saya bukan admin disini**\n__Saya pergi dari sini, tambahkan aku kembali dengan perijinan banned pengguna.__" + ) + await app.leave_chat(message.chat.id) + break + except FloodWait as e: + await sleep(e.value) + try: + await sent_message.edit( + f"✔️ **Berhasil menendang {count} pengguna berdasarkan argumen.**" + ) + + except ChatWriteForbidden: + await app.leave_chat(message.chat.id) + else: + sent_message = await message.reply_text( + "❗ **You have to be the group creator to do that.**" + ) + await sleep(5) + await sent_message.delete() + + +@app.on_message( + filters.incoming & ~filters.private & filters.command(["dkick"], COMMAND_HANDLER) +) +async def dkick(client, message): + user = await app.get_chat_member(message.chat.id, message.from_user.id) + if user.status.value in ("administrator", "owner"): + sent_message = await message.reply_text( + "🚮**Sedang membersihkan user, mungkin butuh waktu beberapa saat...**" + ) + count = 0 + async for member in app.get_chat_members(message.chat.id): + if member.user.is_deleted and member.status.value not in ( + "administrator", + "owner", + ): + try: + await message.chat.ban_member(member.user.id) + count += 1 + await sleep(1) + await message.chat.unban_member(member.user.id) + except (ChatAdminRequired, UserAdminInvalid): + await sent_message.edit( + "❗**Oh tidaakk, saya bukan admin disini**\n__Saya pergi dari sini, tambahkan aku kembali dengan perijinan banned pengguna.__" + ) + await app.leave_chat(message.chat.id) + break + except FloodWait as e: + await sleep(e.value) + try: + await sent_message.edit(f"✔️ **Berhasil menendang {count} akun terhapus.**") + except ChatWriteForbidden: + await app.leave_chat(message.chat.id) + else: + sent_message = await message.reply_text( + "❗ **Kamu harus jadi admin atau owner grup untuk melakukan tindakan ini.**" + ) + await sleep(5) + await sent_message.delete() + + +@app.on_message( + filters.incoming & ~filters.private & filters.command(["instatus"], COMMAND_HANDLER) +) +async def instatus(client, message): + start_time = time.perf_counter() + user = await app.get_chat_member(message.chat.id, message.from_user.id) + count = await app.get_chat_members_count(message.chat.id) + if user.status in ( + enums.ChatMemberStatus.ADMINISTRATOR, + enums.ChatMemberStatus.OWNER, + ): + sent_message = await message.reply_text( + "**Sedang mengumpulkan informasi pengguna...**" + ) + recently = 0 + within_week = 0 + within_month = 0 + long_time_ago = 0 + deleted_acc = 0 + premium_acc = 0 + no_username = 0 + restricted = 0 + banned = 0 + uncached = 0 + bot = 0 + async for ban in app.get_chat_members( + message.chat.id, filter=enums.ChatMembersFilter.BANNED + ): + banned += 1 + async for restr in app.get_chat_members( + message.chat.id, filter=enums.ChatMembersFilter.RESTRICTED + ): + restricted += 1 + async for member in app.get_chat_members(message.chat.id): + user = member.user + if user.is_deleted: + deleted_acc += 1 + elif user.is_bot: + bot += 1 + elif user.is_premium: + premium_acc += 1 + elif not user.username: + no_username += 1 + elif user.status.value == "recently": + recently += 1 + elif user.status.value == "last_week": + within_week += 1 + elif user.status.value == "last_month": + within_month += 1 + elif user.status.value == "long_ago": + long_time_ago += 1 + else: + uncached += 1 + end_time = time.perf_counter() + timelog = "{:.2f}".format(end_time - start_time) + await sent_message.edit( + "💠 {}\n👥 {} Anggota\n——————\n👁‍🗨 Informasi Status Anggota\n——————\n🕒 recently: {}\n🕒 last_week: {}\n🕒 last_month: {}\n🕒 long_ago: {}\n🉑 Tanpa Username: {}\n🤐 Dibatasi: {}\n🚫 Diblokir: {}\n👻 Deleted Account (/dkick): {}\n🤖 Bot: {}\n⭐️ Premium User: {}\n👽 UnCached: {}\n\n⏱ Waktu eksekusi {} detik.".format( + message.chat.title, + count, + recently, + within_week, + within_month, + long_time_ago, + no_username, + restricted, + banned, + deleted_acc, + bot, + premium_acc, + uncached, + timelog, + ) + ) + else: + sent_message = await message.reply_text( + "❗ **Kamu harus jadi admin atau owner grup untuk melakukan tindakan ini.**" + ) + await sleep(5) + await sent_message.delete() diff --git a/misskaty/plugins/inline_search.py b/misskaty/plugins/inline_search.py new file mode 100644 index 00000000..36d6584b --- /dev/null +++ b/misskaty/plugins/inline_search.py @@ -0,0 +1,607 @@ +import json, traceback +from sys import version as pyver, platform +from misskaty import app, user +from motor import version as mongover +from misskaty.plugins.misc_tools import get_content +from pyrogram import __version__ as pyrover +from misskaty.helper.http import http +from misskaty.helper.tools import GENRES_EMOJI +from pyrogram import filters, enums +from bs4 import BeautifulSoup +from utils import demoji +from pykeyboard import InlineKeyboard +from deep_translator import GoogleTranslator +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + InlineQuery, + InlineQueryResultArticle, + InputTextMessageContent, + InlineQueryResultPhoto, +) + +__MODULE__ = "InlineFeature" +__HELP__ = """ +To use this feature, just type bot username with following args below. +~ imdb [query] - Search movie details in IMDb.com. +~ pypi [query] - Search package from Pypi. +~ git [query] - Search in Git. +~ google [query] - Search in Google. +""" + +keywords_list = ["imdb", "pypi", "git", "google", "secretmsg"] + +PRVT_MSGS = {} + + +@app.on_inline_query() +async def inline_menu(_, inline_query: InlineQuery): + if inline_query.query.strip().lower().strip() == "": + buttons = InlineKeyboard(row_width=2) + buttons.add( + *[ + (InlineKeyboardButton(text=i, switch_inline_query_current_chat=i)) + for i in keywords_list + ] + ) + + btn = InlineKeyboard(row_width=2) + bot_state = "Alive" if await app.get_me() else "Dead" + ubot_state = "Alive" if await user.get_me() else "Dead" + btn.add( + InlineKeyboardButton("Stats", callback_data="stats_callback"), + InlineKeyboardButton("Go Inline!", switch_inline_query_current_chat=""), + ) + + msg = f""" +**[MissKaty✨](https://github.com/yasirarism):** +**MainBot:** `{bot_state}` +**UserBot:** `{ubot_state}` +**Python:** `{pyver.split()[0]}` +**Pyrogram:** `{pyrover}` +**MongoDB:** `{mongover}` +**Platform:** `{platform}` +**Profiles:** {(await app.get_me()).username} | {(await user.get_me()).first_name} + """ + answerss = [ + InlineQueryResultArticle( + title="Inline Commands", + description="Help Related To Inline Usage.", + input_message_content=InputTextMessageContent( + "Click A Button To Get Started." + ), + thumb_url="https://hamker.me/cy00x5x.png", + reply_markup=buttons, + ), + InlineQueryResultArticle( + title="Github Repo", + description="Github Repo of This Bot.", + input_message_content=InputTextMessageContent( + "Github Repo @MissKatyRoBot\n\nhttps://github.com/yasirarism/MissKatyPyro" + ), + thumb_url="https://hamker.me/gjc9fo3.png", + ), + InlineQueryResultArticle( + title="Alive", + description="Check Bot's Stats", + thumb_url="https://yt3.ggpht.com/ytc/AMLnZu-zbtIsllERaGYY8Aecww3uWUASPMjLUUEt7ecu=s900-c-k-c0x00ffffff-no-rj", + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + reply_markup=btn, + ), + ] + await inline_query.answer(results=answerss) + elif inline_query.query.strip().lower().split()[0] == "google": + if len(inline_query.query.strip().lower().split()) < 2: + return await inline_query.answer( + results=[], + switch_pm_text="Google Search | google [QUERY]", + switch_pm_parameter="inline", + ) + judul = inline_query.query.split(None, 1)[1].strip() + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/61.0.3163.100 Safari/537.36" + } + search_results = await http.get( + f"https://www.google.com/search?q={judul}&num=20", headers=headers + ) + soup = BeautifulSoup(search_results.text, "lxml") + data = [] + for result in soup.select(".tF2Cxc"): + title = result.select_one(".DKV0Md").text + link = result.select_one(".yuRUbf a")["href"] + try: + snippet = result.select_one("#rso .lyLwlc").text + except: + snippet = "-" + message_text = f"{title}\n" + message_text += f"Deskription: {snippet}" + data.append( + InlineQueryResultArticle( + title=f"{title}", + input_message_content=InputTextMessageContent( + message_text=message_text, + parse_mode=enums.ParseMode.HTML, + disable_web_page_preview=False, + ), + url=link, + description=snippet, + thumb_url="https://te.legra.ph/file/ed8ea62ae636793000bb4.jpg", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="Open Website", url=link)]] + ), + ) + ) + await inline_query.answer( + results=data, + is_gallery=False, + is_personal=False, + next_offset="", + switch_pm_text=f"Found {len(data)} results", + switch_pm_parameter="google", + ) + elif inline_query.query.strip().lower().split()[0] == "secretmsg": + if len(inline_query.query.strip().lower().split()) < 3: + return await inline_query.answer( + results=[], + switch_pm_text="SecretMsg | secretmsg [USERNAME/ID] [MESSAGE]", + switch_pm_parameter="inline", + ) + _id = inline_query.query.split()[1] + msg = inline_query.query.split(None, 2)[2].strip() + + if not msg or not msg.endswith(":"): + inline_query.stop_propagation() + + try: + penerima = await app.get_users(_id.strip()) + except Exception: # pylint: disable=broad-except + inline_query.stop_propagation() + return + + PRVT_MSGS[inline_query.id] = ( + penerima.id, + penerima.first_name, + inline_query.from_user.id, + msg.strip(": "), + ) + prvte_msg = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "Show Message 🔐", callback_data=f"prvtmsg({inline_query.id})" + ) + ], + [ + InlineKeyboardButton( + "Destroy☠️ this msg", + callback_data=f"destroy({inline_query.id})", + ) + ], + ] + ) + mention = ( + f"@{penerima.username}" + if penerima.username + else f"{penerima.first_name}" + ) + + msg_c = ( + f"🔒 A private message to {mention} [{penerima.id}], " + ) + msg_c += "Only he/she can open it." + results = [ + InlineQueryResultArticle( + title=f"A Private Msg to {penerima.first_name}", + input_message_content=InputTextMessageContent(msg_c), + description="Only he/she can open it", + thumb_url="https://te.legra.ph/file/16133ab3297b3f73c8da5.png", + reply_markup=prvte_msg, + ) + ] + await inline_query.answer(results=results, cache_time=3) + elif inline_query.query.strip().lower().split()[0] == "git": + if len(inline_query.query.strip().lower().split()) < 2: + return await inline_query.answer( + results=[], + switch_pm_text="Github Search | git [QUERY]", + switch_pm_parameter="inline", + ) + query = inline_query.query.split(None, 1)[1].strip() + search_results = await http.get( + f"https://api.github.com/search/repositories?q={query}" + ) + srch_results = json.loads(search_results.text) + item = srch_results.get("items") + data = [] + for sraeo in item: + title = sraeo.get("full_name") + link = sraeo.get("html_url") + deskripsi = sraeo.get("description") + lang = sraeo.get("language") + message_text = f"🔗: {sraeo.get('html_url')}\n│\n└─🍴Forks: {sraeo.get('forks')} ┃┃ 🌟Stars: {sraeo.get('stargazers_count')}\n\n" + message_text += f"Description: {deskripsi}\n" + message_text += f"Language: {lang}" + data.append( + InlineQueryResultArticle( + title=f"{title}", + input_message_content=InputTextMessageContent( + message_text=message_text, + parse_mode=enums.ParseMode.HTML, + disable_web_page_preview=False, + ), + url=link, + description=deskripsi, + thumb_url="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="Open Github Link", url=link)]] + ), + ) + ) + await inline_query.answer( + results=data, + is_gallery=False, + is_personal=False, + next_offset="", + switch_pm_text=f"Found {len(data)} results", + switch_pm_parameter="github", + ) + elif inline_query.query.strip().lower().split()[0] == "pypi": + if len(inline_query.query.strip().lower().split()) < 2: + return await inline_query.answer( + results=[], + switch_pm_text="Pypi Search | pypi [QUERY]", + switch_pm_parameter="inline", + ) + query = inline_query.query.split(None, 1)[1].strip() + search_results = await http.get( + f"https://api.hayo.my.id/api/pypi?package={query}" + ) + srch_results = json.loads(search_results.text) + data = [] + for sraeo in srch_results: + title = sraeo.get("title") + link = sraeo.get("link") + deskripsi = sraeo.get("desc") + version = sraeo.get("version") + message_text = f"{title} {version}\n" + message_text += f"Description: {deskripsi}\n" + data.append( + InlineQueryResultArticle( + title=f"{title}", + input_message_content=InputTextMessageContent( + message_text=message_text, + parse_mode=enums.ParseMode.HTML, + disable_web_page_preview=False, + ), + url=link, + description=deskripsi, + thumb_url="https://raw.githubusercontent.com/github/explore/666de02829613e0244e9441b114edb85781e972c/topics/pip/pip.png", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="Open Link", url=link)]] + ), + ) + ) + await inline_query.answer( + results=data, + is_gallery=False, + is_personal=False, + next_offset="", + switch_pm_text=f"Found {len(data)} results", + switch_pm_parameter="pypi", + ) + elif inline_query.query.strip().lower().split()[0] == "yt": + if len(inline_query.query.strip().lower().split()) < 2: + return await inline_query.answer( + results=[], + switch_pm_text="YouTube Search | yt [QUERY]", + switch_pm_parameter="inline", + ) + judul = inline_query.query.split(None, 1)[1].strip() + search_results = await http.get( + f"https://api.abir-hasan.tk/youtube?query={judul}" + ) + srch_results = json.loads(search_results.text) + asroe = srch_results.get("results") + oorse = [] + for sraeo in asroe: + title = sraeo.get("title") + link = sraeo.get("link") + view = sraeo.get("viewCount").get("text") + thumb = sraeo.get("thumbnails")[0].get("url") + durasi = sraeo.get("accessibility").get("duration") + publishTime = sraeo.get("publishedTime") + try: + deskripsi = "".join( + f"{i['text']} " for i in sraeo.get("descriptionSnippet") + ) + except: + deskripsi = "-" + message_text = f"{title}\n" + message_text += f"Description: {deskripsi}\n" + message_text += f"Total View: {view}\n" + message_text += f"Duration: {durasi}\n" + message_text += f"Published Time: {publishTime}" + oorse.append( + InlineQueryResultArticle( + title=f"{title}", + input_message_content=InputTextMessageContent( + message_text=message_text, + parse_mode=enums.ParseMode.HTML, + disable_web_page_preview=False, + ), + url=link, + description=deskripsi, + thumb_url=thumb, + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton(text="Watch Video 📹", url=link)]] + ), + ) + ) + await inline_query.answer( + results=oorse, + is_gallery=False, + is_personal=False, + next_offset="", + switch_pm_text=f"Found {len(asroe)} results", + switch_pm_parameter="yt", + ) + elif inline_query.query.strip().lower().split()[0] == "imdb": + if len(inline_query.query.strip().lower().split()) < 2: + return await inline_query.answer( + results=[], + switch_pm_text="IMDB Search | imdb [QUERY]", + switch_pm_parameter="inline", + ) + movie_name = inline_query.query.split(None, 1)[1].strip() + search_results = await http.get( + f"https://yasirapi.eu.org/imdb-search?q={movie_name}" + ) + res = json.loads(search_results.text).get("result") + oorse = [] + for midb in res: + title = midb.get("l", "") + description = midb.get("q", "") + stars = midb.get("s", "") + imdb_url = f"https://imdb.com/title/{midb.get('id')}" + year = f"({midb.get('y')})" if midb.get("y") else "" + image_url = ( + midb.get("i").get("imageUrl").replace(".jpg", "._V1_UX360.jpg") + if midb.get("i") + else "https://te.legra.ph/file/e263d10ff4f4426a7c664.jpg" + ) + caption = f"🎬" + caption += f"{title} {year}" + oorse.append( + InlineQueryResultPhoto( + title=f"{title} {year}", + caption=caption, + description=f" {description} | {stars}", + photo_url=image_url, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="Get IMDB details", + callback_data=f"imdbinl_{inline_query.from_user.id}_{midb.get('id')}", + ) + ] + ] + ), + ) + ) + resfo = json.loads(search_results.text).get("q") + await inline_query.answer( + results=oorse, + is_gallery=False, + is_personal=False, + next_offset="", + switch_pm_text=f"Found {len(oorse)} results for {resfo}", + switch_pm_parameter="imdb", + ) + + +@app.on_callback_query(filters.regex(r"prvtmsg\((.+)\)")) +async def prvt_msg(_, c_q): + msg_id = str(c_q.matches[0].group(1)) + + if msg_id not in PRVT_MSGS: + await c_q.answer("Message now outdated !", show_alert=True) + return + + user_id, flname, sender_id, msg = PRVT_MSGS[msg_id] + + if c_q.from_user.id in [user_id, sender_id]: + await c_q.answer(msg, show_alert=True) + else: + await c_q.answer(f"Only {flname} can see this Private Msg!", show_alert=True) + + +@app.on_callback_query(filters.regex(r"destroy\((.+)\)")) +async def destroy_msg(_, c_q): + msg_id = str(c_q.matches[0].group(1)) + + if msg_id not in PRVT_MSGS: + await c_q.answer("Message now outdated !", show_alert=True) + return + + user_id, flname, sender_id, msg = PRVT_MSGS[msg_id] + + if c_q.from_user.id in [user_id, sender_id]: + del PRVT_MSGS[msg_id] + by = "receiver" if c_q.from_user.id == user_id else "sender" + await c_q.edit_message_text(f"This secret message is ☠️destroyed☠️ by msg {by}") + else: + await c_q.answer(f"Only {flname} can see this Private Msg!", show_alert=True) + + +@app.on_callback_query(filters.regex("^imdbinl_")) +async def imdb_inl(_, query): + i, user, movie = query.data.split("_") + if user == f"{query.from_user.id}": + await query.edit_message_caption("⏳ Permintaan kamu sedang diproses.. ") + try: + url = f"https://www.imdb.com/title/{movie}/" + resp = await get_content(url) + sop = BeautifulSoup(resp, "lxml") + r_json = json.loads( + sop.find("script", attrs={"type": "application/ld+json"}).contents[0] + ) + res_str = "" + type = f"{r_json['@type']}" if r_json.get("@type") else "" + if r_json.get("name"): + try: + tahun = ( + sop.select('ul[data-testid="hero-title-block__metadata"]')[0] + .find(class_="sc-8c396aa2-2 itZqyK") + .text + ) + except: + tahun = "-" + res_str += f"📹 Judul: {r_json['name']} [{tahun}] ({type})\n" + if r_json.get("alternateName"): + res_str += ( + f"📢 AKA: {r_json.get('alternateName')}\n\n" + ) + else: + res_str += "\n" + if sop.select('li[data-testid="title-techspec_runtime"]'): + durasi = ( + sop.select('li[data-testid="title-techspec_runtime"]')[0] + .find(class_="ipc-metadata-list-item__content-container") + .text + ) + res_str += f"Durasi: {GoogleTranslator('auto', 'id').translate(durasi)}\n" + if r_json.get("contentRating"): + res_str += f"Kategori: {r_json['contentRating']} \n" + if r_json.get("aggregateRating"): + res_str += f"Peringkat: {r_json['aggregateRating']['ratingValue']}⭐️ dari {r_json['aggregateRating']['ratingCount']} pengguna \n" + if sop.select('li[data-testid="title-details-releasedate"]'): + rilis = ( + sop.select('li[data-testid="title-details-releasedate"]')[0] + .find( + class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link" + ) + .text + ) + rilis_url = sop.select('li[data-testid="title-details-releasedate"]')[ + 0 + ].find( + class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link" + )[ + "href" + ] + res_str += f"Rilis: {rilis}\n" + if r_json.get("genre"): + genre = "".join( + f"{GENRES_EMOJI[i]} #{i.replace('-', '_').replace(' ', '_')}, " + if i in GENRES_EMOJI + else f"#{i.replace('-', '_').replace(' ', '_')}, " + for i in r_json["genre"] + ) + + genre = genre[:-2] + res_str += f"Genre: {genre}\n" + if sop.select('li[data-testid="title-details-origin"]'): + country = "".join( + f"{demoji(country.text)} #{country.text.replace(' ', '_').replace('-', '_')}, " + for country in sop.select('li[data-testid="title-details-origin"]')[ + 0 + ].findAll( + class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link" + ) + ) + country = country[:-2] + res_str += f"Negara: {country}\n" + if sop.select('li[data-testid="title-details-languages"]'): + language = "".join( + f"#{lang.text.replace(' ', '_').replace('-', '_')}, " + for lang in sop.select('li[data-testid="title-details-languages"]')[ + 0 + ].findAll( + class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link" + ) + ) + language = language[:-2] + res_str += f"Bahasa: {language}\n" + res_str += "\n🙎 Info Cast:\n" + if r_json.get("director"): + director = "" + for i in r_json["director"]: + name = i["name"] + url = i["url"] + director += f"{name}, " + director = director[:-2] + res_str += f"Sutradara: {director}\n" + if r_json.get("creator"): + creator = "" + for i in r_json["creator"]: + if i["@type"] == "Person": + name = i["name"] + url = i["url"] + creator += f"{name}, " + creator = creator[:-2] + res_str += f"Penulis: {creator}\n" + if r_json.get("actor"): + actors = "" + for i in r_json["actor"]: + name = i["name"] + url = i["url"] + actors += f"{name}, " + actors = actors[:-2] + res_str += f"Pemeran: {actors}\n\n" + if r_json.get("description"): + summary = GoogleTranslator("auto", "id").translate( + r_json.get("description") + ) + res_str += f"📜 Plot: {summary}\n\n" + if r_json.get("keywords"): + keywords = r_json["keywords"].split(",") + key_ = "" + for i in keywords: + i = i.replace(" ", "_").replace("-", "_") + key_ += f"#{i}, " + key_ = key_[:-2] + res_str += f"🔥 Kata Kunci: {key_} \n" + if sop.select('li[data-testid="award_information"]'): + awards = ( + sop.select('li[data-testid="award_information"]')[0] + .find(class_="ipc-metadata-list-item__list-content-item") + .text + ) + res_str += f"🏆 Penghargaan: {GoogleTranslator('auto', 'id').translate(awards)}\n\n" + else: + res_str += "\n" + res_str += "©️ IMDb by @MissKatyRoBot" + if r_json.get("trailer"): + trailer_url = r_json["trailer"]["url"] + markup = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "🎬 Open IMDB", + url=f"https://www.imdb.com{r_json['url']}", + ), + InlineKeyboardButton("▶️ Trailer", url=trailer_url), + ] + ] + ) + else: + markup = InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "🎬 Open IMDB", + url=f"https://www.imdb.com{r_json['url']}", + ) + ] + ] + ) + await query.edit_message_caption(res_str, reply_markup=markup) + except Exception: + exc = traceback.format_exc() + await query.edit_message_caption(f"ERROR:\n{exc}") + else: + await query.answer("⚠️ Akses Ditolak!", True) diff --git a/misskaty/plugins/json.py b/misskaty/plugins/json.py new file mode 100644 index 00000000..43d791fa --- /dev/null +++ b/misskaty/plugins/json.py @@ -0,0 +1,25 @@ +import os +from pyrogram import filters +from misskaty import app +from misskaty.vars import COMMAND_HANDLER + + +# View Structure Telegram Message As JSON +@app.on_message(filters.command(["json"], COMMAND_HANDLER)) +async def jsonify(_, message): + the_real_message = None + reply_to_id = None + + the_real_message = message.reply_to_message or message + try: + await message.reply_text(f"{the_real_message}") + except Exception as e: + with open("json.text", "w+", encoding="utf8") as out_file: + out_file.write(str(the_real_message)) + await message.reply_document( + document="json.text", + caption=f"{str(e)}", + disable_notification=True, + reply_to_message_id=reply_to_id, + ) + os.remove("json.text") diff --git a/misskaty/plugins/karma.py b/misskaty/plugins/karma.py new file mode 100644 index 00000000..9c0bfcdf --- /dev/null +++ b/misskaty/plugins/karma.py @@ -0,0 +1,201 @@ +import re +from pyrogram import filters +from misskaty import app +from misskaty.core.decorator.errors import capture_err +from misskaty.core.decorator.permissions import adminsOnly +from misskaty.helper.functions import alpha_to_int, int_to_alpha +from database.karma_db import ( + get_karma, + get_karmas, + is_karma_on, + karma_off, + karma_on, + update_karma, +) + +__MODULE__ = "Karma" +__HELP__ = """ +Give reputation to other people in group. + +/karma_toggle [enable/disable] - Enable/Disable Karma. +/karma - View all karma from member group. +""" + +karma_positive_group = 3 +karma_negative_group = 4 + +regex_upvote = ( + r"^(\+|\+\+|\+1|thx|tnx|ty|thank you|thanx|thanks|pro|cool|good|makasih|👍|\+\+ .+)$" +) +regex_downvote = r"^(-|--|-1|👎|-- .+)$" + +n = "\n" +w = " " + +bold = lambda x: f"**{x}:** " +bold_ul = lambda x: f"**--{x}:**-- " + +mono = lambda x: f"`{x}`{n}" + + +def section( + title: str, + body: dict, + indent: int = 2, + underline: bool = False, +) -> str: + text = (bold_ul(title) + n) if underline else bold(title) + n + + for key, value in body.items(): + text += ( + indent * w + + bold(key) + + ((value[0] + n) if isinstance(value, list) else mono(value)) + ) + return text + + +async def get_user_id_and_usernames(client) -> dict: + with client.storage.lock, client.storage.conn: + users = client.storage.conn.execute( + 'SELECT * FROM peers WHERE type in ("user", "bot") AND username NOT null' + ).fetchall() + return {user[0]: user[3] for user in users} + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_upvote, re.IGNORECASE) + & ~filters.via_bot + & ~filters.bot, + group=karma_positive_group, +) +@capture_err +async def upvote(_, message): + if not await is_karma_on(message.chat.id): + return + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + chat_id = message.chat.id + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma + 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"Incremented Karma of {user_mention} By 1 \nTotal Points: {karma}" + ) + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_downvote, re.IGNORECASE) + & ~filters.via_bot + & ~filters.bot, + group=karma_negative_group, +) +@capture_err +async def downvote(_, message): + if not await is_karma_on(message.chat.id): + return + if not message.reply_to_message.from_user: + return + if not message.from_user: + return + if message.reply_to_message.from_user.id == message.from_user.id: + return + + chat_id = message.chat.id + user_id = message.reply_to_message.from_user.id + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma - 1 + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"Decremented Karma Of {user_mention} By 1 \nTotal Points: {karma}" + ) + + +@app.on_message(filters.command("karma") & filters.group) +@capture_err +async def command_karma(_, message): + chat_id = message.chat.id + if not message.reply_to_message: + m = await message.reply_text("Analyzing Karma...") + karma = await get_karmas(chat_id) + if not karma: + return await m.edit("No karma in DB for this chat.") + msg = f"Karma list of {message.chat.title}" + limit = 0 + karma_dicc = {} + for i in karma: + user_id = await alpha_to_int(i) + user_karma = karma[i]["karma"] + karma_dicc[str(user_id)] = user_karma + karma_arranged = dict( + sorted( + karma_dicc.items(), + key=lambda item: item[1], + reverse=True, + ) + ) + if not karma_dicc: + return await m.edit("No karma in DB for this chat.") + userdb = await get_user_id_and_usernames(app) + karma = {} + for user_idd, karma_count in karma_arranged.items(): + if limit > 15: + break + if int(user_idd) not in list(userdb.keys()): + continue + username = userdb[int(user_idd)] + karma[f"@{username}"] = [f"**{str(karma_count)}**"] + limit += 1 + await m.edit(section(msg, karma)) + else: + if not message.reply_to_message.from_user: + return await message.reply("Anon user hash no karma.") + + user_id = message.reply_to_message.from_user.id + karma = await get_karma(chat_id, await int_to_alpha(user_id)) + karma = karma["karma"] if karma else 0 + await message.reply_text(f"**Total Points**: __{karma}__") + + +@app.on_message(filters.command("karma_toggle") & ~filters.private) +@adminsOnly +async def captcha_state(_, message): + usage = "**Usage:**\n/karma_toggle [ENABLE|DISABLE]" + if len(message.command) != 2: + return await message.reply_text(usage) + chat_id = message.chat.id + state = message.text.split(None, 1)[1].strip() + state = state.lower() + if state == "enable": + await karma_on(chat_id) + await message.reply_text("Enabled karma system.") + elif state == "disable": + await karma_off(chat_id) + await message.reply_text("Disabled karma system.") + else: + await message.reply_text(usage) diff --git a/misskaty/plugins/mediainfo.py b/misskaty/plugins/mediainfo.py new file mode 100644 index 00000000..7c172b8d --- /dev/null +++ b/misskaty/plugins/mediainfo.py @@ -0,0 +1,81 @@ +import io +from os import remove as osremove +import time +import subprocess +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from misskaty.vars import COMMAND_HANDLER +from utils import get_file_id +from misskaty import app +from misskaty.helper.media_helper import post_to_telegraph, runcmd +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.pyro_progress import ( + progress_for_pyrogram, +) + + +@app.on_message(filters.command(["mediainfo", "mediainfo@MissKatyRoBot"], COMMAND_HANDLER)) +@capture_err +async def mediainfo(client, message): + if message.reply_to_message and message.reply_to_message.media: + process = await message.reply_text("`Sedang memproses, lama waktu tergantung ukuran file kamu...`", quote=True) + file_info = get_file_id(message.reply_to_message) + if file_info is None: + await process.edit_text("Balas ke format media yang valid") + return + c_time = time.time() + # file_path = safe_filename(await reply.download()) + file_path = await client.download_media( + message=message.reply_to_message, + progress=progress_for_pyrogram, + progress_args=("trying to download, sabar yakk..", process, c_time), + ) + output_ = await runcmd(f'mediainfo "{file_path}"') + out = output_[0] if len(output_) != 0 else None + body_text = f""" + +

JSON

+
{file_info}.type
+
+

DETAILS

+
{out or 'Not Supported'}
+ """ + title = "MissKaty Bot Mediainfo" + text_ = file_info.message_type + link = post_to_telegraph(title, body_text) + markup = InlineKeyboardMarkup([[InlineKeyboardButton(text=text_, url=link)]]) + await message.reply("ℹ️ MEDIA INFO", reply_markup=markup, quote=True) + await process.delete() + try: + osremove(file_path) + except Exception: + pass + else: + try: + link = message.text.split(" ", maxsplit=1)[1] + if link.startswith("https://file.yasirweb.my.id"): + link = link.replace("https://file.yasirweb.my.id", "https://file.yasiraris.workers.dev") + if link.startswith("https://link.yasirweb.my.id"): + link = link.replace("https://link.yasirweb.my.id", "https://yasirrobot.herokuapp.com") + process = await message.reply_text("`Mohon tunggu sejenak...`") + try: + output = subprocess.check_output(["mediainfo", f"{link}"]).decode("utf-8") + except Exception: + return await process.edit("Sepertinya link yang kamu kirim tidak valid, pastikan direct link dan bisa di download.") + title = "MissKaty Bot Mediainfo" + body_text = f""" + +
{output}
+ """ + tgraph = post_to_telegraph(title, body_text) + # siteurl = "https://spaceb.in/api/v1/documents/" + # response = await http.post(siteurl, data={"content": output, "extension": 'txt'} ) + # response = response.json() + # spacebin = "https://spaceb.in/"+response['payload']['id'] + markup = InlineKeyboardMarkup([[InlineKeyboardButton(text="💬 Telegraph", url=tgraph)]]) + with io.BytesIO(str.encode(output)) as out_file: + out_file.name = "MissKaty_Mediainfo.txt" + await message.reply_document(out_file, caption=f"Hasil mediainfo anda..\n\nRequest by: {message.from_user.mention}", reply_markup=markup) + await process.delete() + except IndexError: + return await message.reply_text("Gunakan command /mediainfo [link], atau reply telegram media dengan /mediainfo.") diff --git a/misskaty/plugins/memify.py b/misskaty/plugins/memify.py new file mode 100644 index 00000000..3884d80d --- /dev/null +++ b/misskaty/plugins/memify.py @@ -0,0 +1,154 @@ +import textwrap +from os import remove as hapus +from misskaty.core.decorator.errors import capture_err +from misskaty import app +from pyrogram import filters +from misskaty.vars import COMMAND_HANDLER +from PIL import Image, ImageFont, ImageDraw + + +async def draw_meme_text(image_path, text): + img = Image.open(image_path) + hapus(image_path) + i_width, i_height = img.size + m_font = ImageFont.truetype("Calistoga-Regular.ttf", int((70 / 640) * i_width)) + if ";" in text: + upper_text, lower_text = text.split(";") + else: + upper_text = text + lower_text = "" + draw = ImageDraw.Draw(img) + current_h, pad = 10, 5 + if upper_text: + for u_text in textwrap.wrap(upper_text, width=15): + u_width, u_height = draw.textsize(u_text, font=m_font) + + draw.text( + xy=(((i_width - u_width) / 2) - 1, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=(((i_width - u_width) / 2) + 1, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=((i_width - u_width) / 2, int(((current_h / 640) * i_width)) - 1), + text=u_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=(((i_width - u_width) / 2), int(((current_h / 640) * i_width)) + 1), + text=u_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + + draw.text( + xy=((i_width - u_width) / 2, int((current_h / 640) * i_width)), + text=u_text, + font=m_font, + fill=(255, 255, 255), + ) + current_h += u_height + pad + if lower_text: + for l_text in textwrap.wrap(lower_text, width=15): + u_width, u_height = draw.textsize(l_text, font=m_font) + + draw.text( + xy=( + ((i_width - u_width) / 2) - 1, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=( + ((i_width - u_width) / 2) + 1, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) - 1, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + draw.text( + xy=( + (i_width - u_width) / 2, + (i_height - u_height - int((20 / 640) * i_width)) + 1, + ), + text=l_text, + font=m_font, + fill=(0, 0, 0), + stroke_width=3, + stroke_fill="black", + ) + + draw.text( + xy=( + (i_width - u_width) / 2, + i_height - u_height - int((20 / 640) * i_width), + ), + text=l_text, + font=m_font, + fill=(255, 255, 255), + stroke_width=3, + stroke_fill="black", + ) + current_h += u_height + pad + + webp_file = "memify.webp" + img.save(webp_file, "WebP") + return webp_file + + +@app.on_message(filters.command(["mmf"], COMMAND_HANDLER)) +@capture_err +async def memify(client, message): + if message.reply_to_message and ( + message.reply_to_message.sticker or message.reply_to_message.photo + ): + try: + file = await message.reply_to_message.download() + res = await draw_meme_text(file, message.text.split(None, 1)[1].strip()) + await message.reply_sticker(res) + try: + hapus(res) + except: + pass + except: + await message.reply( + "Gunakan command /mmf dengan reply ke sticker, pisahkan dengan ; untuk membuat posisi text dibawah." + ) + else: + await message.reply( + "Gunakan command /mmf dengan reply ke sticker, pisahkan dengan ; untuk membuat posisi text dibawah." + ) diff --git a/misskaty/plugins/misc_tools.py b/misskaty/plugins/misc_tools.py new file mode 100644 index 00000000..fe80d9cc --- /dev/null +++ b/misskaty/plugins/misc_tools.py @@ -0,0 +1,645 @@ +import os, re +import aiohttp +from bs4 import BeautifulSoup +import json +import traceback +from pyrogram import Client, filters +from deep_translator import GoogleTranslator +from gtts import gTTS +from pyrogram.errors import MediaEmpty, MessageNotModified, PhotoInvalidDimensions, UserNotParticipant, WebpageMediaEmpty, MessageTooLong +from misskaty.vars import COMMAND_HANDLER +from utils import extract_user, get_file_id, demoji +import time +from datetime import datetime +from pykeyboard import InlineKeyboard +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.tools import rentry, GENRES_EMOJI +from misskaty.helper.http import http +from misskaty import app +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.ERROR) + +__MODULE__ = "Misc" +__HELP__ = """ +/sof [query] - Search your problem in StackOverflow. +/google [query] - Search using Google Search. +(/tr, /trans, /translate) [lang code] - Translate text using Google Translate. +/tts - Convert Text to Voice. +/imdb [query] - Find Movie Details From IMDB.com in Indonesian Language. +/imdb_en [query] - Find Movie Details From IMDB.com in English Language. +""" + + +def remove_html_tags(text): + """Remove html tags from a string""" + import re + + clean = re.compile("<.*?>") + return re.sub(clean, "", text) + + +@app.on_message(filters.command(["sof"], COMMAND_HANDLER)) +@capture_err +async def stackoverflow(client, message): + if len(message.command) == 1: + return await message.reply("Give a query to search in StackOverflow!") + r = (await http.get(f"https://api.stackexchange.com/2.3/search/excerpts?order=asc&sort=relevance&q={message.command[1]}&accepted=True&migrated=False¬ice=False&wiki=False&site=stackoverflow")).json() + hasil = "" + for count, data in enumerate(r["items"], start=1): + question = data["question_id"] + title = data["title"] + snippet = remove_html_tags(data["excerpt"])[:80].replace("\n", "").replace(" ", "") if len(remove_html_tags(data["excerpt"])) > 80 else remove_html_tags(data["excerpt"]).replace("\n", "").replace(" ", "") + hasil += f"{count}. {title}\n{snippet}\n" + try: + await message.reply(hasil) + except MessageTooLong: + url = await rentry(hasil) + await r.edit(f"Your text pasted to rentry because has long text:\n{url}") + except Exception as e: + await message.reply(e) + + +@app.on_message(filters.command(["google"], COMMAND_HANDLER)) +@capture_err +async def gsearch(client, message): + if len(message.command) == 1: + return await message.reply("Give a query to search in Google!") + query = message.text.split(" ", maxsplit=1)[1] + msg = await message.reply_text(f"**Googling** for `{query}` ...") + try: + headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/61.0.3163.100 Safari/537.36"} + html = await http.get(f"https://www.google.com/search?q={query}&gl=id&hl=id&num=17", headers=headers) + soup = BeautifulSoup(html.text, "lxml") + + # collect data + data = [] + + for result in soup.select(".tF2Cxc"): + title = result.select_one(".DKV0Md").text + link = result.select_one(".yuRUbf a")["href"] + try: + snippet = result.select_one("#rso .lyLwlc").text + except: + snippet = "-" + + # appending data to an array + data.append( + { + "title": title, + "link": link, + "snippet": snippet, + } + ) + arr = json.dumps(data, indent=2, ensure_ascii=False) + parse = json.loads(arr) + total = len(parse) + res = "".join(f"{i['title']}\n{i['snippet']}\n\n" for i in parse) + except Exception: + exc = traceback.format_exc() + return await msg.edit(exc) + await msg.edit(text=f"Ada {total} Hasil Pencarian dari {query}:\n{res}Scraped by @MissKatyRoBot", disable_web_page_preview=True) + + +@app.on_message(filters.command(["tr", "trans", "translate"], COMMAND_HANDLER)) +@capture_err +async def translate(client, message): + if message.reply_to_message and (message.reply_to_message.text or message.reply_to_message.caption): + target_lang = "id" if len(message.command) == 1 else message.text.split()[1] + text = message.reply_to_message.text or message.reply_to_message.caption + else: + if len(message.command) == 1: + return await message.reply_text( + "Berikan Kode bahasa yang valid.\n[Available options](https://telegra.ph/Lang-Codes-11-08).\nUsage: /tr en", + ) + target_lang = message.text.split(None, 2)[1] + text = message.text.split(None, 2)[2] + msg = await message.reply("Menerjemahkan...") + try: + tekstr = (await http.get(f"https://script.google.com/macros/s/AKfycbyhNk6uVgrtJLEFRUT6y5B2pxETQugCZ9pKvu01-bE1gKkDRsw/exec?q={text}&target={target_lang}")).json()["text"] + except Exception as err: + return await msg.edit(f"Error: {str(err)}") + try: + await msg.edit(f"{tekstr}") + except MessageTooLong: + url = await rentry(tekstr.text) + await msg.edit(f"Your translated text pasted to rentry because has long text:\n{url}") + + +@app.on_message(filters.command(["tts"], COMMAND_HANDLER)) +@capture_err +async def tts(_, message): + if message.reply_to_message and (message.reply_to_message.text or message.reply_to_message.caption): + if len(message.text.split()) == 1: + target_lang = "id" + else: + target_lang = message.text.split()[1] + text = message.reply_to_message.text or message.reply_to_message.caption + else: + if len(message.text.split()) <= 2: + await message.reply_text( + "Berikan Kode bahasa yang valid.\n[Available options](https://telegra.ph/Lang-Codes-11-08).\nUsage: /tts en ", + ) + return + target_lang = message.text.split(None, 2)[1] + text = message.text.split(None, 2)[2] + msg = await message.reply("Converting to voice...") + try: + tts = gTTS(text, lang=target_lang) + tts.save(f"tts_{message.from_user.id}.mp3") + except ValueError as err: + await msg.edit(f"Error: {str(err)}") + return + await msg.delete() + await msg.reply_audio(f"tts_{message.from_user.id}.mp3") + try: + os.remove(f"tts_{message.from_user.id}.mp3") + except: + pass + + +@app.on_message(filters.command(["tosticker", "tosticker@MissKatyRoBot"], COMMAND_HANDLER)) +@capture_err +async def tostick(client, message): + try: + if not message.reply_to_message or not message.reply_to_message.photo: + return await message.reply_text("Reply ke foto untuk mengubah ke sticker") + sticker = await client.download_media(message.reply_to_message.photo.file_id, f"tostick_{message.from_user.id}.webp") + await message.reply_sticker(sticker) + os.remove(sticker) + except Exception as e: + await message.reply_text(str(e)) + + +@app.on_message(filters.command(["toimage", "toimage@MissKatyRoBot"], COMMAND_HANDLER)) +@capture_err +async def topho(client, message): + try: + if not message.reply_to_message or not message.reply_to_message.sticker: + return await message.reply_text("Reply ke sticker untuk mengubah ke foto") + if message.reply_to_message.sticker.is_animated: + return await message.reply_text("Ini sticker animasi, command ini hanya untuk sticker biasa.") + photo = await client.download_media(message.reply_to_message.sticker.file_id, f"tostick_{message.from_user.id}.jpg") + await message.reply_photo(photo=photo, caption="Sticker -> Image\n@MissKatyRoBot") + + os.remove(photo) + except Exception as e: + await message.reply_text(str(e)) + + +@app.on_message(filters.command(["id", "id@MissKatyRoBot"], COMMAND_HANDLER)) +async def showid(client, message): + chat_type = message.chat.type + if chat_type == "private": + user_id = message.chat.id + first = message.from_user.first_name + last = message.from_user.last_name or "" + username = message.from_user.username + dc_id = message.from_user.dc_id or "" + await message.reply_text(f"➲ First Name: {first}\n➲ Last Name: {last}\n➲ Username: {username}\n➲ Telegram ID: {user_id}\n➲ Data Centre: {dc_id}", quote=True) + + elif chat_type in ["group", "supergroup"]: + _id = "" + _id += "➲ Chat ID: " f"{message.chat.id}\n" + if message.reply_to_message: + _id += ( + "➲ User ID: " + f"{message.from_user.id if message.from_user else 'Anonymous'}\n" + "➲ Replied User ID: " + f"{message.reply_to_message.from_user.id if message.reply_to_message.from_user else 'Anonymous'}\n" + ) + file_info = get_file_id(message.reply_to_message) + else: + _id += "➲ User ID: " f"{message.from_user.id if message.from_user else 'Anonymous'}\n" + file_info = get_file_id(message) + if file_info: + _id += f"{file_info.message_type}: " f"{file_info.file_id}\n" + await message.reply_text(_id, quote=True) + + +@app.on_message(filters.command(["info"], COMMAND_HANDLER)) +async def who_is(client, message): + # https://github.com/SpEcHiDe/PyroGramBot/blob/master/pyrobot/plugins/admemes/whois.py#L19 + status_message = await message.reply_text("`Fetching user info...`") + await status_message.edit("`Processing user info...`") + from_user = None + from_user_id, _ = extract_user(message) + try: + from_user = await client.get_users(from_user_id) + except Exception as error: + await status_message.edit(str(error)) + return + if from_user is None: + return await status_message.edit("no valid user_id / message specified") + message_out_str = "" + message_out_str += f"➲First Name: {from_user.first_name}\n" + last_name = from_user.last_name or "None" + message_out_str += f"➲Last Name: {last_name}\n" + message_out_str += f"➲Telegram ID: {from_user.id}\n" + username = from_user.username or "None" + dc_id = from_user.dc_id or "[User Doesnt Have A Valid DP]" + message_out_str += f"➲Data Centre: {dc_id}\n" + message_out_str += f"➲User Name: @{username}\n" + message_out_str += f"➲User 𝖫𝗂𝗇𝗄: Click Here\n" + if message.chat.type in (("supergroup", "channel")): + try: + chat_member_p = await message.chat.get_member(from_user.id) + joined_date = datetime.fromtimestamp(chat_member_p.joined_date or time.time()).strftime("%Y.%m.%d %H:%M:%S") + message_out_str += "➲Joined this Chat on: " f"{joined_date}" "\n" + except UserNotParticipant: + pass + if chat_photo := from_user.photo: + local_user_photo = await client.download_media(message=chat_photo.big_file_id) + buttons = [[InlineKeyboardButton("🔐 Close", callback_data="close_data")]] + reply_markup = InlineKeyboardMarkup(buttons) + await message.reply_photo(photo=local_user_photo, quote=True, reply_markup=reply_markup, caption=message_out_str, disable_notification=True) + os.remove(local_user_photo) + else: + buttons = [[InlineKeyboardButton("🔐 Close", callback_data="close_data")]] + reply_markup = InlineKeyboardMarkup(buttons) + await message.reply_text(text=message_out_str, reply_markup=reply_markup, quote=True, disable_notification=True) + await status_message.delete() + + +headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10"} + + +async def get_content(url): + async with aiohttp.ClientSession() as session: + r = await session.get(url, headers=headers) + return await r.read() + + +async def mdlapi(title): + link = f"https://kuryana.vercel.app/search/q/{title}" + async with aiohttp.ClientSession() as ses: + async with ses.get(link) as result: + return await result.json() + + +@app.on_message(filters.command(["mdl"], COMMAND_HANDLER)) +@capture_err +async def mdlsearch(client, message): + if " " in message.text: + r, title = message.text.split(None, 1) + k = await message.reply("Sedang mencari di Database MyDramaList.. 😴") + movies = await mdlapi(title) + res = movies["results"]["dramas"] + if not movies: + return await k.edit("Tidak ada hasil ditemukan.. 😕") + btn = [ + [ + InlineKeyboardButton( + text=f"{movie.get('title')} ({movie.get('year')})", + callback_data=f"mdls_{message.from_user.id}_{message.id}_{movie['slug']}", + ) + ] + for movie in res + ] + await k.edit(f"Ditemukan {len(movies)} query dari {title}", reply_markup=InlineKeyboardMarkup(btn)) + else: + await message.reply("Berikan aku nama drama yang ingin dicari. 🤷🏻‍♂️") + + +@app.on_callback_query(filters.regex("^mdls")) +@capture_err +async def mdl_callback(bot: Client, query: CallbackQuery): + i, user, msg_id, slug = query.data.split("_") + if user == f"{query.from_user.id}": + await query.message.edit_text("Permintaan kamu sedang diproses.. ") + result = "" + try: + res = (await http.get(f"https://kuryana.vercel.app/id/{slug}")).json() + result += f"Title: {res['data']['title']}\n" + result += f"AKA: {res['data']['others']['also_known_as']}\n\n" + result += f"Rating: {res['data']['details']['score']}\n" + result += f"Content Rating: {res['data']['details']['content_rating']}\n" + result += f"Type: {res['data']['details']['type']}\n" + result += f"Country: {res['data']['details']['country']}\n" + if res["data"]["details"]["type"] == "Movie": + result += f"Release Date: {res['data']['details']['release_date']}\n" + elif res["data"]["details"]["type"] == "Drama": + result += f"Episode: {res['data']['details']['episodes']}\n" + result += f"Aired: {res['data']['details']['aired']}\n" + try: + result += f"Aired on: {res['data']['details']['aired_on']}\n" + except: + pass + try: + result += f"Original Network: {res['data']['details']['original_network']}\n" + except: + pass + result += f"Duration: {res['data']['details']['duration']}\n" + result += f"Genre: {res['data']['others']['genres']}\n\n" + result += f"Synopsis: {res['data']['synopsis']}\n" + result += f"Tags: {res['data']['others']['tags']}\n" + btn = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Open MyDramaList", url=res["data"]["link"])]]) + await query.message.edit_text(result, reply_markup=btn) + except Exception as e: + await query.message.edit_text(f"ERROR:\n{e}") + else: + await query.answer("Tombol ini bukan untukmu", show_alert=True) + + +# IMDB Versi Indonesia v1 +@app.on_message(filters.command(["imdb"], COMMAND_HANDLER)) +@capture_err +async def imdb1_search(client, message): + BTN = [] + if message.sender_chat: + return await message.reply("Mohon maaf fitur tidak tersedia untuk akun channel, harap ganti ke akun biasa..") + if len(message.command) == 1: + return await message.reply("Berikan aku nama series atau movie yang ingin dicari. 🤷🏻‍♂️", quote=True) + r, judul = message.text.split(None, 1) + k = await message.reply("🔎 Sedang mencari di Database IMDB..", quote=True) + msg = "" + buttons = InlineKeyboard(row_width=4) + try: + r = await get_content(f"https://yasirapi.eu.org/imdb-search?q={judul}") + res = json.loads(r).get("result") + if not res: + return await k.edit("Tidak ada hasil ditemukan.. 😕") + msg += f"Ditemukan {len(res)} query dari {judul} ~ {message.from_user.mention}\n\n" + for count, movie in enumerate(res, start=1): + title = movie.get("l") + year = f"({movie.get('y')})" if movie.get("y") else "" + type = movie.get("q").replace("feature", "movie").capitalize() + movieID = re.findall(r"tt(\d+)", movie.get("id"))[0] + msg += f"{count}. {title} {year} ~ {type}\n" + BTN.append(InlineKeyboardButton(text=count, callback_data=f"imdbid#{message.from_user.id}#{movieID}")) + buttons.add(*BTN) + await k.edit(msg, reply_markup=buttons) + except Exception as err: + await k.edit(f"Ooppss, gagal mendapatkan daftar judul di IMDb.\n\nERROR: {err}") + + +@app.on_callback_query(filters.regex("^imdbid")) +async def imdbcb_backup(bot: Client, query: CallbackQuery): + usr = query.message.reply_to_message + i, userid, movie = query.data.split("#") + if query.from_user.id != int(userid): + return await query.answer("⚠️ Akses Ditolak!", True) + try: + await query.message.edit_text("Permintaan kamu sedang diproses.. ") + url = f"https://www.imdb.com/title/tt{movie}/" + resp = await get_content(url) + sop = BeautifulSoup(resp, "lxml") + r_json = json.loads(sop.find("script", attrs={"type": "application/ld+json"}).contents[0]) + res_str = "" + type = f"{r_json['@type']}" if r_json.get("@type") else "" + if r_json.get("name"): + try: + tahun = sop.select('ul[data-testid="hero-title-block__metadata"]')[0].find(class_="sc-8c396aa2-2 itZqyK").text + except: + tahun = "-" + res_str += f"📹 Judul: {r_json['name']} [{tahun}] ({type})\n" + if r_json.get("alternateName"): + res_str += f"📢 AKA: {r_json.get('alternateName')}\n\n" + else: + res_str += "\n" + if sop.select('li[data-testid="title-techspec_runtime"]'): + durasi = sop.select('li[data-testid="title-techspec_runtime"]')[0].find(class_="ipc-metadata-list-item__content-container").text + res_str += f"Durasi: {GoogleTranslator('auto', 'id').translate(durasi)}\n" + if r_json.get("contentRating"): + res_str += f"Kategori: {r_json['contentRating']} \n" + if r_json.get("aggregateRating"): + res_str += f"Peringkat: {r_json['aggregateRating']['ratingValue']}⭐️ dari {r_json['aggregateRating']['ratingCount']} pengguna \n" + if sop.select('li[data-testid="title-details-releasedate"]'): + rilis = sop.select('li[data-testid="title-details-releasedate"]')[0].find(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link").text + rilis_url = sop.select('li[data-testid="title-details-releasedate"]')[0].find(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link")["href"] + res_str += f"Rilis: {rilis}\n" + if r_json.get("genre"): + genre = "".join(f"{GENRES_EMOJI[i]} #{i.replace('-', '_').replace(' ', '_')}, " if i in GENRES_EMOJI else f"#{i.replace('-', '_').replace(' ', '_')}, " for i in r_json["genre"]) + + genre = genre[:-2] + res_str += f"Genre: {genre}\n" + if sop.select('li[data-testid="title-details-origin"]'): + country = "".join( + f"{demoji(country.text)} #{country.text.replace(' ', '_').replace('-', '_')}, " + for country in sop.select('li[data-testid="title-details-origin"]')[0].findAll(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link") + ) + country = country[:-2] + res_str += f"Negara: {country}\n" + if sop.select('li[data-testid="title-details-languages"]'): + language = "".join( + f"#{lang.text.replace(' ', '_').replace('-', '_')}, " for lang in sop.select('li[data-testid="title-details-languages"]')[0].findAll(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link") + ) + language = language[:-2] + res_str += f"Bahasa: {language}\n" + res_str += "\n🙎 Info Cast:\n" + if r_json.get("director"): + director = "" + for i in r_json["director"]: + name = i["name"] + url = i["url"] + director += f"{name}, " + director = director[:-2] + res_str += f"Sutradara: {director}\n" + if r_json.get("creator"): + creator = "" + for i in r_json["creator"]: + if i["@type"] == "Person": + name = i["name"] + url = i["url"] + creator += f"{name}, " + creator = creator[:-2] + res_str += f"Penulis: {creator}\n" + if r_json.get("actor"): + actors = "" + for i in r_json["actor"]: + name = i["name"] + url = i["url"] + actors += f"{name}, " + actors = actors[:-2] + res_str += f"Pemeran: {actors}\n\n" + if r_json.get("description"): + summary = GoogleTranslator("auto", "id").translate(r_json.get("description")) + res_str += f"📜 Plot: {summary}\n\n" + if r_json.get("keywords"): + keywords = r_json["keywords"].split(",") + key_ = "" + for i in keywords: + i = i.replace(" ", "_").replace("-", "_") + key_ += f"#{i}, " + key_ = key_[:-2] + res_str += f"🔥 Kata Kunci: {key_} \n" + if sop.select('li[data-testid="award_information"]'): + awards = sop.select('li[data-testid="award_information"]')[0].find(class_="ipc-metadata-list-item__list-content-item").text + res_str += f"🏆 Penghargaan: {GoogleTranslator('auto', 'id').translate(awards)}\n\n" + else: + res_str += "\n" + res_str += "©️ IMDb by @MissKatyRoBot" + if r_json.get("trailer"): + trailer_url = r_json["trailer"]["url"] + markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Open IMDB", url=f"https://www.imdb.com{r_json['url']}"), InlineKeyboardButton("▶️ Trailer", url=trailer_url)]]) + else: + markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Open IMDB", url=f"https://www.imdb.com{r_json['url']}")]]) + if thumb := r_json.get("image"): + try: + await query.message.reply_photo(photo=thumb, quote=True, caption=res_str, reply_to_message_id=usr.id, reply_markup=markup) + except (MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty): + poster = thumb.replace(".jpg", "._V1_UX360.jpg") + await query.message.reply_photo(photo=poster, caption=res_str, reply_to_message_id=usr.id, reply_markup=markup) + except Exception: + await query.message.reply(res_str, reply_markup=markup, disable_web_page_preview=False, reply_to_message_id=usr.id) + await query.message.delete() + else: + await query.message.edit(res_str, reply_markup=markup, disable_web_page_preview=False) + await query.answer() + except MessageNotModified: + pass + except Exception: + exc = traceback.format_exc() + await query.message.edit_text(f"ERROR:\n{exc}") + + +# IMDB Versi English +@app.on_message(filters.command(["imdb_en"], COMMAND_HANDLER)) +@capture_err +async def imdb_en_search(client, message): + BTN = [] + if message.sender_chat: + return await message.reply("This feature not available for channel.") + if len(message.command) == 1: + return await message.reply("Give movie name or series. Ex: /imdb_en soul. 🤷🏻‍♂️", quote=True) + r, title = message.text.split(None, 1) + k = await message.reply("Searching Movie/Series in IMDB Database.. 😴", quote=True) + msg = "" + buttons = InlineKeyboard(row_width=4) + try: + r = await get_content(f"https://yasirapi.eu.org/imdb-search?q={title}") + res = json.loads(r).get("result") + if not res: + return await k.edit("Sad, No Result.. 😕") + msg = f"Found {len(res)} result from {title} ~ {message.from_user.mention}\n\n" + for count, movie in enumerate(res, start=1): + titles = movie.get("l") + year = f"({movie.get('y')})" if movie.get("y") else "" + type = movie.get("qid").replace("feature", "movie").capitalize() + movieID = re.findall(r"tt(\d+)", movie.get("id"))[0] + msg += f"{count}. {titles} {year} ~ {type}\n" + BTN.append(InlineKeyboardButton(text=count, callback_data=f"imdben#{message.from_user.id}#{movieID}")) + buttons.add(*BTN) + await k.edit(msg, reply_markup=buttons) + except Exception as err: + await k.edit(f"Ooppss, failed get movie list from IMDb.\n\nERROR: {err}") + + +@app.on_callback_query(filters.regex("^imdben")) +@capture_err +async def imdb_en_callback(bot: Client, query: CallbackQuery): + usr = query.message.reply_to_message + i, userid, movie = query.data.split("#") + if query.from_user.id != int(userid): + return await query.answer("⚠️ Access Denied!", True) + await query.message.edit_text("⏳ Processing your request..") + try: + url = f"https://www.imdb.com/title/tt{movie}/" + resp = await get_content(url) + sop = BeautifulSoup(resp, "lxml") + r_json = json.loads(sop.find("script", attrs={"type": "application/ld+json"}).contents[0]) + res_str = "" + type = f"{r_json['@type']}" if r_json.get("@type") else "" + if r_json.get("name"): + try: + tahun = sop.select('ul[data-testid="hero-title-block__metadata"]')[0].find(class_="sc-8c396aa2-2 itZqyK").text + except: + tahun = "-" + res_str += f"📹 Title: {r_json['name']} [{tahun}] ({type})\n" + if r_json.get("alternateName"): + res_str += f"📢 AKA: {r_json.get('alternateName')}\n\n" + else: + res_str += "\n" + if sop.select('li[data-testid="title-techspec_runtime"]'): + durasi = sop.select('li[data-testid="title-techspec_runtime"]')[0].find(class_="ipc-metadata-list-item__content-container").text + res_str += f"Duration: {durasi}\n" + if r_json.get("contentRating"): + res_str += f"Category: {r_json['contentRating']} \n" + if r_json.get("aggregateRating"): + res_str += f"Rating: {r_json['aggregateRating']['ratingValue']}⭐️ from {r_json['aggregateRating']['ratingCount']} user \n" + if sop.select('li[data-testid="title-details-releasedate"]'): + rilis = sop.select('li[data-testid="title-details-releasedate"]')[0].find(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link").text + rilis_url = sop.select('li[data-testid="title-details-releasedate"]')[0].find(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link")["href"] + res_str += f"Release Data: {rilis}\n" + if r_json.get("genre"): + genre = "".join(f"{GENRES_EMOJI[i]} #{i.replace('-', '_').replace(' ', '_')}, " if i in GENRES_EMOJI else f"#{i.replace('-', '_').replace(' ', '_')}, " for i in r_json["genre"]) + + genre = genre[:-2] + res_str += f"Genre: {genre}\n" + if sop.select('li[data-testid="title-details-origin"]'): + country = "".join( + f"{demoji(country.text)} #{country.text.replace(' ', '_').replace('-', '_')}, " + for country in sop.select('li[data-testid="title-details-origin"]')[0].findAll(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link") + ) + country = country[:-2] + res_str += f"Country: {country}\n" + if sop.select('li[data-testid="title-details-languages"]'): + language = "".join( + f"#{lang.text.replace(' ', '_').replace('-', '_')}, " for lang in sop.select('li[data-testid="title-details-languages"]')[0].findAll(class_="ipc-metadata-list-item__list-content-item ipc-metadata-list-item__list-content-item--link") + ) + language = language[:-2] + res_str += f"Language: {language}\n" + res_str += "\n🙎 Cast Info:\n" + if r_json.get("director"): + director = "" + for i in r_json["director"]: + name = i["name"] + url = i["url"] + director += f"{name}, " + director = director[:-2] + res_str += f"Director: {director}\n" + if r_json.get("creator"): + creator = "" + for i in r_json["creator"]: + if i["@type"] == "Person": + name = i["name"] + url = i["url"] + creator += f"{name}, " + creator = creator[:-2] + res_str += f"Penulis: {creator}\n" + if r_json.get("actor"): + actors = "" + for i in r_json["actor"]: + name = i["name"] + url = i["url"] + actors += f"{name}, " + actors = actors[:-2] + res_str += f"Stars: {actors}\n\n" + if r_json.get("description"): + res_str += f"📜 Summary: {r_json['description'].replace(' ', ' ')}\n\n" + if r_json.get("keywords"): + keywords = r_json["keywords"].split(",") + key_ = "" + for i in keywords: + i = i.replace(" ", "_").replace("-", "_") + key_ += f"#{i}, " + key_ = key_[:-2] + res_str += f"🔥 Keywords: {key_} \n" + if sop.select('li[data-testid="award_information"]'): + awards = sop.select('li[data-testid="award_information"]')[0].find(class_="ipc-metadata-list-item__list-content-item").text + res_str += f"🏆 Awards: {awards}\n\n" + else: + res_str += "\n" + res_str += "©️ IMDb by @MissKatyRoBot" + if r_json.get("trailer"): + trailer_url = r_json["trailer"]["url"] + markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Open IMDB", url=f"https://www.imdb.com{r_json['url']}"), InlineKeyboardButton("▶️ Trailer", url=trailer_url)]]) + else: + markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Open IMDB", url=f"https://www.imdb.com{r_json['url']}")]]) + if thumb := r_json.get("image"): + try: + await query.message.reply_photo(photo=thumb, quote=True, caption=res_str, reply_to_message_id=usr.id, reply_markup=markup) + except (MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty): + poster = thumb.replace(".jpg", "._V1_UX360.jpg") + await query.message.reply_photo(photo=poster, caption=res_str, reply_to_message_id=usr.id, reply_markup=markup) + except Exception: + await query.message.reply(res_str, reply_markup=markup, disable_web_page_preview=False, reply_to_message_id=usr.id) + await query.message.delete() + else: + await query.message.edit(res_str, reply_markup=markup, disable_web_page_preview=False) + await query.answer() + except Exception: + exc = traceback.format_exc() + await query.message.edit_text(f"ERROR:\n{exc}") diff --git a/misskaty/plugins/nightmode.py b/misskaty/plugins/nightmode.py new file mode 100644 index 00000000..9d07da5d --- /dev/null +++ b/misskaty/plugins/nightmode.py @@ -0,0 +1,213 @@ +# Auto Close and Open Group, I dont have time to add Database Support +from pyrogram.types import ChatPermissions +from pyrogram import Client, __version__, filters +from apscheduler.schedulers.asyncio import AsyncIOScheduler +import pytz +import traceback +from misskaty import app +from datetime import datetime +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery +from misskaty.vars import LOG_CHANNEL + + +# Check calculate how long it will take to Ramadhan +def puasa(): + now = datetime.now(pytz.timezone("Asia/Jakarta")) + tahun = now.strftime("%Y") + bulan = now.strftime("%m") + tgl = now.strftime("%d") + jam = now.strftime("%H") + menit = now.strftime("%M") + detik = now.strftime("%S") + x = datetime(int(tahun), int(bulan), int(tgl), int(jam), int(menit), int(detik)) + y = datetime(2022, 4, 2, 0, 0, 0) + return y - x + + +async def job_close(): + now = datetime.now(pytz.timezone("Asia/Jakarta")) + days = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"] + month = [ + "Unknown", + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ] + tgl = now.strftime("%d") + tahun = now.strftime("%Y") + jam = now.strftime("%H:%M") + try: + # version = check_output(["git log -1 --date=format:v%y.%m%d.%H%M --pretty=format:%cd"], shell=True).decode() + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="❤️", callback_data="nightmd")]] + ) + await app.set_chat_permissions( + -1001128045651, + ChatPermissions(can_send_messages=False, can_invite_users=True), + ) + await app.send_message( + -1001128045651, + f"📆 {days[now.weekday()]}, {tgl} {month[now.month]} {tahun}\n⏰ Jam : {jam}\n\n**🌗 Mode Malam Aktif**\n`Grup ditutup dan semua member tidak akan bisa mengirim pesan. Selamat beristirahat dan bermimpi indah !!`", + reply_markup=reply_markup, + ) + except Exception: + exc = traceback.format_exc() + await app.send_message(LOG_CHANNEL, f"ERROR:\n{exc}") + + +async def job_close_ymoviez(): + now = datetime.now(pytz.timezone("Asia/Jakarta")) + days = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"] + month = [ + "Unknown", + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ] + tgl = now.strftime("%d") + tahun = now.strftime("%Y") + jam = now.strftime("%H:%M") + try: + # version = check_output(["git log -1 --date=format:v%y.%m%d.%H%M --pretty=format:%cd"], shell=True).decode() + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="❤️", callback_data="nightmd")]] + ) + await app.set_chat_permissions( + -1001255283935, + ChatPermissions(can_send_messages=False, can_invite_users=True), + ) + await app.send_message( + -1001255283935, + f"📆 {days[now.weekday()]}, {tgl} {month[now.month]} {tahun}\n⏰ Jam : {jam}\n\n**🌗 Mode Malam Aktif**\n`Grup ditutup hingga jam 9 pagi. Selamat beristirahat.....`", + ) + except Exception: + exc = traceback.format_exc() + await app.send_message(LOG_CHANNEL, f"ERROR:\n{exc}") + + +async def job_open(): + now = datetime.now(pytz.timezone("Asia/Jakarta")) + days = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"] + month = [ + "Unknown", + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ] + tgl = now.strftime("%d") + tahun = now.strftime("%Y") + jam = now.strftime("%H:%M") + try: + # version = check_output(["git log -1 --date=format:v%y.%m%d.%H%M --pretty=format:%cd"], shell=True).decode() + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="❤️", callback_data="nightmd")]] + ) + await app.set_chat_permissions( + -1001128045651, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_invite_users=True, + can_add_web_page_previews=True, + can_send_other_messages=False, + ), + ) + await app.send_message( + -1001128045651, + f"📆 {days[now.weekday()]}, {tgl} {month[now.month]} {tahun}\n⏰ {jam}`\n\n🌗 Mode Malam Selesai\nSelamat pagi, grup kini telah dibuka semoga hari-harimu menyenangkan.`", + reply_markup=reply_markup, + ) + except Exception: + exc = traceback.format_exc() + await app.send_message(LOG_CHANNEL, f"ERROR:\n{exc}") + + +async def job_open_ymoviez(): + now = datetime.now(pytz.timezone("Asia/Jakarta")) + days = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"] + month = [ + "Unknown", + "Januari", + "Februari", + "Maret", + "April", + "Mei", + "Juni", + "Juli", + "Agustus", + "September", + "Oktober", + "November", + "Desember", + ] + tgl = now.strftime("%d") + tahun = now.strftime("%Y") + jam = now.strftime("%H:%M") + try: + # version = check_output(["git log -1 --date=format:v%y.%m%d.%H%M --pretty=format:%cd"], shell=True).decode() + reply_markup = InlineKeyboardMarkup( + [[InlineKeyboardButton(text="❤️", callback_data="nightmd")]] + ) + await app.set_chat_permissions( + -1001255283935, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_invite_users=True, + can_add_web_page_previews=True, + can_send_other_messages=True, + ), + ) + await app.send_message( + -1001255283935, + f"📆 {days[now.weekday()]}, {tgl} {month[now.month]} {tahun}\n⏰ {jam}`\n\n🌗 Mode Malam Selesai\nSelamat pagi, grup kini telah dibuka semoga hari-harimu menyenangkan.`", + reply_markup=reply_markup, + ) + except Exception: + exc = traceback.format_exc() + await app.send_message(LOG_CHANNEL, f"ERROR:\n{exc}") + + +@app.on_callback_query(filters.regex(r"^nightmd$")) +async def _callbackanightmd(c: Client, q: CallbackQuery): + # version = check_output(["git log -1 --date=format:v%y.%m%d.%H%M --pretty=format:%cd"], shell=True).decode() + await q.answer( + f"🔖 Hai, Aku MissKatyRoBot dibuat menggunakan Framework Pyrogram v{__version__} dan Python 3.10.\n\nMau buat bot seperti ini? Yuuk belajar di @botindonesia\nOwner: @YasirArisM", + show_alert=True, + cache_time=21600, + ) + + +scheduler = AsyncIOScheduler(timezone="Asia/Jakarta") +scheduler.add_job(job_close, trigger="cron", hour=22, minute=0) +scheduler.add_job(job_close_ymoviez, trigger="cron", hour=22, minute=0) +scheduler.add_job(job_open, trigger="cron", hour=6, minute=0) +scheduler.add_job(job_open_ymoviez, trigger="cron", hour=10, minute=0) +scheduler.start() diff --git a/misskaty/plugins/ocr.py b/misskaty/plugins/ocr.py new file mode 100644 index 00000000..893d3389 --- /dev/null +++ b/misskaty/plugins/ocr.py @@ -0,0 +1,38 @@ +import os +from pyrogram import filters +from telegraph import upload_file +from misskaty.vars import COMMAND_HANDLER +from misskaty import app +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.http import http + +__MODULE__ = "OCR" +__HELP__ = "/ocr [reply to photo] - Read Text From Image" + + +@app.on_message(filters.command(["ocr"], COMMAND_HANDLER)) +@capture_err +async def ocr(_, message): + reply = message.reply_to_message + if not reply or not reply.photo and not reply.sticker: + return await message.reply_text( + f"Reply photo with /{message.command[0]} command" + ) + msg = await message.reply("Reading image...") + try: + file_path = await reply.download() + if reply.sticker: + file_path = await reply.download(f"ocr{message.from_user.id}.jpg") + response = upload_file(file_path) + url = f"https://telegra.ph{response[0]}" + req = ( + await http.get( + f"https://script.google.com/macros/s/AKfycbwURISN0wjazeJTMHTPAtxkrZTWTpsWIef5kxqVGoXqnrzdLdIQIfLO7jsR5OQ5GO16/exec?url={url}", + follow_redirects=True + ) + ).json() + await msg.edit(f"Hasil OCR:\n{req['text']}") + os.remove(file_path) + except Exception as e: + await msg.edit(str(e)) + os.remove(file_path) diff --git a/misskaty/plugins/paste.py b/misskaty/plugins/paste.py new file mode 100644 index 00000000..4385115a --- /dev/null +++ b/misskaty/plugins/paste.py @@ -0,0 +1,195 @@ +from os import remove +from re import compile as compiles +from misskaty.helper.http import http +from pyrogram import filters +from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from json import loads as json_loads +from misskaty import app +from misskaty.vars import COMMAND_HANDLER +from misskaty.helper.tools import rentry + +__MODULE__ = "Paste" +__HELP__ = """ +/paste [Text/Reply To Message] - Post text to Rentry using markdown style. +/temp_paste [Text/Reply To Message] - Post text to tempaste.com using html style. +""" + +# Size Checker for Limit +def humanbytes(size: int): + """Convert Bytes To Bytes So That Human Can Read It""" + if not isinstance(command, int): + try: + size = size + except ValueError: + size = None + if not size: + return "0 B" + size = int(size) + power = 2**10 + raised_to_pow = 0 + dict_power_n = { + 0: "", + 1: "K", + 2: "M", + 3: "G", + 4: "T", + 5: "P", + 6: "E", + 7: "Z", + 8: "Y", + } + while size > power: + size /= power + raised_to_pow += 1 + try: + real_size = f"{str(round(size, 2))} {dict_power_n[raised_to_pow]}B" + except KeyError: + real_size = "Can't Define Real Size !" + return real_size + + +# Pattern if extension supported, PR if want to add more +pattern = compiles(r"^text/|json$|yaml$|xml$|toml$|x-sh$|x-shellscript$|x-subrip$") + + +@app.on_message(filters.command(["paste"], COMMAND_HANDLER)) +async def create(_, message): + reply = message.reply_to_message + target = str(message.command[0]).split("@", maxsplit=1)[0] + if not reply and len(message.command) < 2: + return await message.reply_text( + f"**Reply To A Message With /{target} or with command**" + ) + + msg = await message.reply_text("`Pasting to Rentry...`") + data = "" + limit = 1024 * 1024 + if reply and reply.document: + if reply.document.file_size > limit: + return await msg.edit( + f"**You can only paste files smaller than {humanbytes(limit)}.**" + ) + if not pattern.search(reply.document.mime_type): + return await msg.edit("**Only text files can be pasted.**") + file = await reply.download() + try: + with open(file, "r") as text: + data = text.read() + remove(file) + except UnicodeDecodeError: + try: + remove(file) + except: + pass + return await msg.edit("`File Not Supported !`") + elif reply and (reply.text or reply.caption): + data = reply.text.markdown or reply.caption.markdown + elif not reply and len(message.command) >= 2: + data = message.text.split(None, 1)[1] + + if message.from_user: + if message.from_user.username: + uname = f"@{message.from_user.username}" + else: + uname = ( + f"[{message.from_user.first_name}](tg://user?id={message.from_user.id})" + ) + else: + uname = message.sender_chat.title + + try: + url = await rentry(data) + except Exception as e: + await msg.edit(f"`{e}`") + return + + if not url: + return await msg.edit("Text Too Short Or File Problems") + button = [[InlineKeyboardButton("Open Link", url=url)]] + button.append( + [ + InlineKeyboardButton( + "Share Link", url=f"https://telegram.me/share/url?url={url}" + ) + ] + ) + + pasted = f"**Successfully pasted your data to Rentry.\n\nPaste by {uname}**" + await msg.edit(pasted, reply_markup=InlineKeyboardMarkup(button)) + + +@app.on_message(filters.command(["temp_paste"], COMMAND_HANDLER)) +async def create(_, message): + reply = message.reply_to_message + target = str(message.command[0]).split("@", maxsplit=1)[0] + if not reply and len(message.command) < 2: + return await message.reply_text( + f"**Reply To A Message With /{target} or with command**" + ) + + msg = await message.reply_text("`Pasting to TempPaste...`") + data = "" + limit = 1024 * 1024 + if reply and reply.document: + if reply.document.file_size > limit: + return await msg.edit( + f"**You can only paste files smaller than {humanbytes(limit)}.**" + ) + if not pattern.search(reply.document.mime_type): + return await msg.edit("**Only text files can be pasted.**") + file = await reply.download() + try: + with open(file, "r") as text: + data = text.read() + remove(file) + except UnicodeDecodeError: + try: + remove(file) + except: + pass + return await msg.edit("`File Not Supported !`") + elif reply and (reply.text or reply.caption): + data = reply.text.html or reply.caption.html + elif not reply and len(message.command) >= 2: + data = message.text.split(None, 1)[1] + + if message.from_user: + if message.from_user.username: + uname = f"@{message.from_user.username}" + else: + uname = ( + f"[{message.from_user.first_name}](tg://user?id={message.from_user.id})" + ) + else: + uname = message.sender_chat.title + + try: + req = await http.post( + "https://tempaste.com/api/v1/create-paste/", + data={ + "api_key": "xnwuzXubxk3kCUz9Q2pjMVR8xeTO4t", + "title": "MissKaty Paste", + "paste_content": data, + "visibility": "public", + "expiry_date_type": "months", + "expiry_date": 12, + }, + ) + url = f"https://tempaste.com/{json_loads(req.text)['url']}" + except Exception as e: + await msg.edit(f"`{e}`") + return + + if not url: + return await msg.edit("Text Too Short Or File Problems") + button = [[InlineKeyboardButton("Open Link", url=url)]] + button.append( + [ + InlineKeyboardButton( + "Share Link", url=f"https://telegram.me/share/url?url={url}" + ) + ] + ) + + pasted = f"**Successfully pasted your data to Tempaste.\n\nPaste by {uname}**" + await msg.edit(pasted, reply_markup=InlineKeyboardMarkup(button)) diff --git a/misskaty/plugins/ping.py b/misskaty/plugins/ping.py new file mode 100644 index 00000000..e2c8ff3e --- /dev/null +++ b/misskaty/plugins/ping.py @@ -0,0 +1,55 @@ +import time +from re import findall, MULTILINE +from asyncio import Lock +from pyrogram import filters +from misskaty.vars import COMMAND_HANDLER +from misskaty import app, botStartTime +from misskaty.helper.human_read import get_readable_time +from subprocess import run as srun + + +@app.on_message(filters.command(["ping"], COMMAND_HANDLER)) +async def ping(_, message): + currentTime = get_readable_time(time.time() - botStartTime) + start_t = time.time() + rm = await message.reply_text("🐱 Pong!!...") + end_t = time.time() + time_taken_s = round(end_t - start_t, 3) + try: + await rm.edit( + f"🐈 MissKatyBot online.\n\nPing: {time_taken_s} detik\nUptime: {currentTime}" + ) + except Exception: + pass + + +@app.on_message(filters.command(["ping_dc"], COMMAND_HANDLER)) +async def ping_handler(_, message): + m = await message.reply("Pinging datacenters...") + async with Lock(): + ips = { + "dc1": "149.154.175.53", + "dc2": "149.154.167.51", + "dc3": "149.154.175.100", + "dc4": "149.154.167.91", + "dc5": "91.108.56.130", + } + text = "**Pings:**\n" + + for dc, ip in ips.items(): + try: + shell = srun( + ["ping", "-c", "1", "-W", "2", ip], + text=True, + check=True, + capture_output=True, + ) + resp_time = findall(r"time=.+m?s", shell.stdout, MULTILINE)[0].replace( + "time=", "" + ) + + text += f" **{dc.upper()}:** {resp_time} ✅\n" + except Exception: + # There's a cross emoji here, but it's invisible. + text += f" **{dc.upper}:** ❌\n" + await m.edit(text) diff --git a/misskaty/plugins/quotly.py b/misskaty/plugins/quotly.py new file mode 100644 index 00000000..5b2c988e --- /dev/null +++ b/misskaty/plugins/quotly.py @@ -0,0 +1,280 @@ +from pyrogram import Client, filters +from pyrogram.types import Message +from misskaty import app +from misskaty.helper.http import http +from io import BytesIO + +__MODULE__ = "Fun" +__HELP__ = """ +/q [int] - Generate quotly from message +/memify [text] - Reply to sticker to give text on sticker. +""" + + +class QuotlyException(Exception): + pass + + +async def get_message_sender_id(m: Message): + if m.forward_date: + if m.forward_sender_name: + return 1 + elif m.forward_from: + return m.forward_from.id + elif m.forward_from_chat: + return m.forward_from_chat.id + else: + return 1 + elif m.from_user: + return m.from_user.id + elif m.sender_chat: + return m.sender_chat.id + else: + return 1 + + +async def get_message_sender_name(m: Message): + if m.forward_date: + if m.forward_sender_name: + return m.forward_sender_name + elif m.forward_from: + return ( + f"{m.forward_from.first_name} {m.forward_from.last_name}" + if m.forward_from.last_name + else m.forward_from.first_name + ) + + elif m.forward_from_chat: + return m.forward_from_chat.title + else: + return "" + elif m.from_user: + if m.from_user.last_name: + return f"{m.from_user.first_name} {m.from_user.last_name}" + else: + return m.from_user.first_name + elif m.sender_chat: + return m.sender_chat.title + else: + return "" + + +async def get_custom_emoji(m: Message): + if m.forward_date: + return ( + "" + if m.forward_sender_name + or not m.forward_from + and m.forward_from_chat + or not m.forward_from + else m.forward_from.emoji_status.custom_emoji_id + ) + + return m.from_user.emoji_status.custom_emoji_id if m.from_user else "" + + +async def get_message_sender_username(m: Message): + if m.forward_date: + if ( + not m.forward_sender_name + and not m.forward_from + and m.forward_from_chat + and m.forward_from_chat.username + ): + return m.forward_from_chat.username + elif ( + not m.forward_sender_name + and not m.forward_from + and m.forward_from_chat + or m.forward_sender_name + or not m.forward_from + ): + return "" + else: + return m.forward_from.username or "" + elif m.from_user and m.from_user.username: + return m.from_user.username + elif ( + m.from_user or m.sender_chat and not m.sender_chat.username or not m.sender_chat + ): + return "" + else: + return m.sender_chat.username + + +async def get_message_sender_photo(m: Message): + if m.forward_date: + if ( + not m.forward_sender_name + and not m.forward_from + and m.forward_from_chat + and m.forward_from_chat.photo + ): + return { + "small_file_id": m.forward_from_chat.photo.small_file_id, + "small_photo_unique_id": m.forward_from_chat.photo.small_photo_unique_id, + "big_file_id": m.forward_from_chat.photo.big_file_id, + "big_photo_unique_id": m.forward_from_chat.photo.big_photo_unique_id, + } + elif ( + not m.forward_sender_name + and not m.forward_from + and m.forward_from_chat + or m.forward_sender_name + or not m.forward_from + ): + return "" + else: + return ( + { + "small_file_id": m.forward_from.photo.small_file_id, + "small_photo_unique_id": m.forward_from.photo.small_photo_unique_id, + "big_file_id": m.forward_from.photo.big_file_id, + "big_photo_unique_id": m.forward_from.photo.big_photo_unique_id, + } + if m.forward_from.photo + else "" + ) + + elif m.from_user and m.from_user.photo: + return { + "small_file_id": m.from_user.photo.small_file_id, + "small_photo_unique_id": m.from_user.photo.small_photo_unique_id, + "big_file_id": m.from_user.photo.big_file_id, + "big_photo_unique_id": m.from_user.photo.big_photo_unique_id, + } + elif m.from_user or m.sender_chat and not m.sender_chat.photo or not m.sender_chat: + return "" + else: + return { + "small_file_id": m.sender_chat.photo.small_file_id, + "small_photo_unique_id": m.sender_chat.photo.small_photo_unique_id, + "big_file_id": m.sender_chat.photo.big_file_id, + "big_photo_unique_id": m.sender_chat.photo.big_photo_unique_id, + } + + +async def get_text_or_caption(m: Message): + if m.text: + return m.text + elif m.caption: + return m.caption + else: + return "" + + +async def pyrogram_to_quotly(messages): + if not isinstance(messages, list): + messages = [messages] + payload = { + "type": "quote", + "format": "png", + "backgroundColor": "#1b1429", + "messages": [], + } + + for message in messages: + the_message_dict_to_append = {} + if message.entities: + the_message_dict_to_append["entities"] = [ + { + "type": entity.type.name.lower(), + "offset": entity.offset, + "length": entity.length, + } + for entity in message.entities + ] + elif message.caption_entities: + the_message_dict_to_append["entities"] = [ + { + "type": entity.type.name.lower(), + "offset": entity.offset, + "length": entity.length, + } + for entity in message.caption_entities + ] + else: + the_message_dict_to_append["entities"] = [] + the_message_dict_to_append["chatId"] = await get_message_sender_id(message) + the_message_dict_to_append["text"] = await get_text_or_caption(message) + the_message_dict_to_append["avatar"] = True + the_message_dict_to_append["from"] = {} + the_message_dict_to_append["from"]["id"] = await get_message_sender_id(message) + the_message_dict_to_append["from"]["name"] = await get_message_sender_name( + message + ) + the_message_dict_to_append["from"][ + "username" + ] = await get_message_sender_username(message) + the_message_dict_to_append["from"]["type"] = message.chat.type.name.lower() + the_message_dict_to_append["from"]["photo"] = await get_message_sender_photo( + message + ) + if message.reply_to_message: + the_message_dict_to_append["replyMessage"] = { + "name": await get_message_sender_name(message.reply_to_message), + "text": await get_text_or_caption(message.reply_to_message), + "chatId": await get_message_sender_id(message.reply_to_message), + } + else: + the_message_dict_to_append["replyMessage"] = {} + payload["messages"].append(the_message_dict_to_append) + r = await http.post("https://bot.lyo.su/quote/generate.png", json=payload) + if not r.is_error: + return r.read() + else: + raise QuotlyException(r.json()) + + +def isArgInt(txt) -> list: + count = txt + try: + count = int(count) + return [True, count] + except ValueError: + return [False, 0] + + +@app.on_message(filters.command(["q"]) & filters.reply) +async def msg_quotly_cmd(c: Client, m: Message): + if len(m.text.split()) > 1: + check_arg = isArgInt(m.command[1]) + if check_arg[0]: + if check_arg[1] < 2 or check_arg[1] > 10: + return await m.reply_text("Invalid range") + try: + messages = [ + i + for i in await c.get_messages( + chat_id=m.chat.id, + message_ids=range( + m.reply_to_message.id, + m.reply_to_message.id + (check_arg[1] + 5), + ), + replies=-1, + ) + if not i.empty and not i.media + ] + except Exception: + return await m.reply_text("🤷🏻‍♂️") + try: + make_quotly = await pyrogram_to_quotly(messages) + bio_sticker = BytesIO(make_quotly) + bio_sticker.name = "biosticker.webp" + return await m.reply_sticker(bio_sticker) + except Exception: + return await m.reply_text("🤷🏻‍♂️") + try: + messages_one = await c.get_messages( + chat_id=m.chat.id, message_ids=m.reply_to_message.id, replies=-1 + ) + messages = [messages_one] + except Exception: + return await m.reply_text("🤷🏻‍♂️") + try: + make_quotly = await pyrogram_to_quotly(messages) + bio_sticker = BytesIO(make_quotly) + bio_sticker.name = "biosticker.webp" + return await m.reply_sticker(bio_sticker) + except Exception as e: + return await m.reply_text(f"ERROR: {e}") diff --git a/misskaty/plugins/scrapwebsite.py b/misskaty/plugins/scrapwebsite.py new file mode 100644 index 00000000..13bfff04 --- /dev/null +++ b/misskaty/plugins/scrapwebsite.py @@ -0,0 +1,417 @@ +# This plugin to scrape from melongmovie, and lk21 +from bs4 import BeautifulSoup +import aiohttp +import re +import traceback +from misskaty import app +from pyrogram import filters +from pyrogram.errors import MessageTooLong +from misskaty.vars import COMMAND_HANDLER +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.tools import rentry +from misskaty.helper.http import http + +__MODULE__ = "WebScraper" +__HELP__ = """ +/melongmovie - Scrape website data from MelongMovie Web. If without query will give latest movie list. +/lk21 [query ] - Scrape website data from LayarKaca21. If without query will give latest movie list. +/terbit21 [query ] - Scrape website data from Terbit21. If without query will give latest movie list. +/savefilm21 [query ] - Scrape website data from Savefilm21. If without query will give latest movie list. +/movieku [query ] - Scrape website data from Movieku.cc +/gomov [query ] - Scrape website data from GoMov. If without query will give latest movie list. +""" + + +# Broken +@app.on_message(filters.command(["nodrakor"], COMMAND_HANDLER)) +@capture_err +async def nodrakor(_, message): + try: + judul = message.text.split(" ", maxsplit=1)[1] + except IndexError: + judul = "" + + msg = await message.reply("Sedang proses scrap, mohon tunggu..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + html = await http.get(f"https://109.234.34.246/?s={judul}", headers=headers) + soup = BeautifulSoup(html.text, "lxml") + res = soup.find_all(class_="content-thumbnail text-center") + data = [] + for i in res: + link = i.find_all("a")[0]["href"] + judul = i.find_all("a")[0]["title"].split(": ")[1] + data.append({"judul": judul, "link": link}) + if not data: + return await msg.edit("Oops, data film tidak ditemukan.") + res = "".join(f"{i['judul']}\n{i['link']}\n\n" for i in data) + await msg.edit( + f"Hasil Pencarian di Nodrakor:\n{res}\nScraped by @MissKatyRoBot" + ) + except Exception as e: + await msg.edit(f"ERROR: {str(e)}") + + +# Broken +@app.on_message(filters.command(["ngefilm21"], COMMAND_HANDLER)) +@capture_err +async def ngefilm21(_, message): + if len(message.command) == 1: + return await message.reply("Masukkan query yang akan dicari..!!") + title = message.text.split(" ", maxsplit=1)[1] + + msg = await message.reply("Sedang proses scrap, mohon tunggu..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get( + f"http://185.237.253.209/search?q={title}", headers=headers + ) + soup = BeautifulSoup(html.text, "lxml") + res = soup.find_all("h2") + data = [] + for i in res: + a = i.find_all("a")[0] + judul = a.find_all(class_="r-snippetized") + b = i.find_all("a")[0]["href"] + data.append({"judul": judul[0].text, "link": b}) + # print(f"{judul[0].text}{b}\n") + if not data: + return await msg.edit("Oops, data film tidak ditemukan.") + res = "".join(f"{i['judul']}\n{i['link']}\n" for i in data) + await msg.edit(f"Hasil Scrap dari Ngefilm21:\n{res}") + except Exception as e: + await msg.edit(f"ERROR: {str(e)}") + + +# Scrape Web From Movieku.CC +@app.on_message(filters.command(["movieku"], COMMAND_HANDLER)) +@capture_err +async def movikucc(_, message): + if len(message.command) == 1: + return await message.reply("Masukkan query yang akan dicari..!!") + judul = message.text.split(" ", maxsplit=1)[1] + msg = await message.reply("Sedang proses scrap, mohon tunggu..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + html = await http.get(f"https://107.152.39.187/?s={judul}", headers=headers) + soup = BeautifulSoup(html.text, "lxml") + data = soup.find_all(class_="bx") + res = "".join( + f"Judul: {i.find_all('a')[0]['title']}\nLink: {i.find_all('a')[0]['href']}\n\n" + for i in data + ) + await msg.edit( + f"Hasil Scrap di Movieku.cc:\n{res} ⚠️ Gunakan command /movieku_scrap [link] untuk mengambil link download (hanya untuk movie)." + ) + except Exception as e: + await msg.edit(f"ERROR: {str(e)}") + + +@app.on_message(filters.command(["savefilm21"], COMMAND_HANDLER)) +@capture_err +async def savefilm21(_, message): + if len(message.command) == 1: + return await message.reply("Masukkan query yang akan dicari..!!") + judul = message.text.split(" ", maxsplit=1)[1] + msg = await message.reply("Sedang proses scrap, mohon tunggu..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get( + f"http://38.242.196.210/?s={judul}", headers=headers, allow_redirects=False + ) + soup = BeautifulSoup(html.text, "lxml") + res = soup.find_all(class_="entry-title") + data = [] + for i in res: + pas = i.find_all("a") + judul = pas[0].text + link = pas[0]["href"] + data.append({"judul": judul, "link": link}) + if not data: + return await msg.edit("Oops, data film tidak ditemukan") + res = "".join( + f"Judul: {i['judul']}\nLink: {i['link']}\n\n" for i in data + ) + await msg.edit( + f"Hasil Scrap {judul} dari Savefilm21:\n{res}\n\n⚠️ Gunakan /savefilm21_scrap [link] untuk mengambil link downloadnya." + ) + except Exception as e: + await msg.edit(f"ERROR: {str(e)}") + + +@app.on_message(filters.command(["melongmovie"], COMMAND_HANDLER)) +@capture_err +async def melongmovie(_, message): + try: + judul = message.text.split(" ", maxsplit=1)[1] + except IndexError: + judul = "" + + msg = await message.reply("Sedang proses scrap, mohon tunggu..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(f"http://167.99.31.48/?s={judul}", headers=headers) + soup = BeautifulSoup(html.text, "lxml") + data = [] + for res in soup.select(".box"): + dd = res.select("a") + url = dd[0]["href"] + title = dd[0]["title"] + try: + kualitas = dd[0].find(class_="quality").text + except: + kualitas = "" + data.append({"judul": title, "link": url, "kualitas": kualitas}) + if not data: + return await msg.edit("Oops, data film tidak ditemukan di melongmovie") + res = "".join( + f"Judul: {i['judul']}\nKualitas: {i['kualitas']}\nLink: {i['link']}\n\n" + for i in data + ) + # return await message.reply(json.dumps(data, indent=2, ensure_ascii=False)) + return await msg.edit(res) + except Exception as e: + await msg.edit(f"ERROR: {str(e)}") + + +@app.on_message(filters.command(["terbit21"], COMMAND_HANDLER)) +@capture_err +async def terbit21_scrap(_, message): + if len(message.command) == 1: + r = await http.get("https://yasirapi.eu.org/terbit21") + res = r.json() + data = "".join( + f"**Judul: {i['judul']}**\n`{i['kategori']}`\n{i['link']}\n**Download:** [Klik Disini]({i['dl']})\n\n" + for i in res["result"] + ) + try: + return await message.reply( + f"**Daftar rilis movie terbaru di web Terbit21**:\n{data}", + disable_web_page_preview=True, + ) + except MessageTooLong: + msg = await rentry(data) + return await message.reply( + f"Karena hasil scrape terlalu panjang, maka hasil scrape di post ke rentry.\n\n{msg}" + ) + judul = message.text.split(" ", maxsplit=1)[1] + msg = await message.reply(f"Mencari film di Terbit21 dg keyword {judul}..") + r = await http.get(f"https://yasirapi.eu.org/terbit21?q={judul}") + res = r.json() + data = "".join( + f"**Judul: {i['judul']}**\n`{i['kategori']}`\n{i['link']}\n**Download:** [Klik Disini]({i['dl']})\n\n" + for i in res["result"] + ) + if not res["result"]: + return await msg.edit("Yahh, ga ada hasil ditemukan") + try: + await msg.edit( + f"Hasil pencarian query {judul} di lk21:\n{data}", + disable_web_page_preview=True, + ) + except MessageTooLong: + pesan = await rentry(data) + await msg.edit( + f"Karena hasil scrape terlalu panjang, maka hasil scrape di post ke rentry.\n\n{pesan}" + ) + + +@app.on_message(filters.command(["lk21"], COMMAND_HANDLER)) +@capture_err +async def lk21_scrap(_, message): + if len(message.command) == 1: + msg = await message.reply("Mendapatkan daftar post film terbaru di lk21") + r = await http.get("https://yasirapi.eu.org/lk21") + res = r.json() + if res.get("detail", None): + return await msg.edit(f"ERROR: {res['detail']}") + data = "".join( + f"**Judul: {i['judul']}**\n`{i['kategori']}`\n{i['link']}\n**Download:** [Klik Disini]({i['dl']})\n\n" + for i in res["result"] + ) + try: + return await msg.edit( + f"**Daftar rilis movie terbaru di web LK21**:\n{data}", + disable_web_page_preview=True, + ) + except MessageTooLong: + msg = await rentry(data) + await msg.edit( + f"Karena hasil scrape terlalu panjang, maka hasil scrape di post ke rentry.\n\n{msg}" + ) + judul = message.text.split(" ", maxsplit=1)[1] + msg = await message.reply(f"Mencari film di lk21 dg keyword {judul}..") + r = await http.get(f"https://yasirapi.eu.org/lk21?q={judul}") + res = r.json() + if res.get("detail", None): + return await msg.edit(f"ERROR: {res['detail']}") + data = "".join( + f"**Judul: {i['judul']}**\n`{i['kategori']}`\n{i['link']}\n**Download:** [Klik Disini]({i['dl']})\n\n" + for i in res["result"] + ) + if not res["result"]: + return await msg.edit("Yahh, ga ada hasil ditemukan") + try: + await msg.edit( + f"Hasil pencarian query {judul} di lk21:\n{data}", + disable_web_page_preview=True, + ) + except MessageTooLong: + pesan = await rentry(data) + return await msg.edit( + f"Karena hasil scrape terlalu panjang, maka hasil scrape di post ke rentry.\n\n{pesan}" + ) + + +@app.on_message(filters.command(["gomov", "gomov@MissKatyRoBot"], COMMAND_HANDLER)) +@capture_err +async def gomov_scrap(_, message): + try: + judul = message.text.split(" ", maxsplit=1)[1] + except IndexError: + judul = "" + + msg = await message.reply("Scraping GoMov Website..") + try: + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(f"https://185.173.38.216/?s={judul}", headers=headers) + soup = BeautifulSoup(html.text, "lxml") + entry = soup.find_all(class_="entry-title") + DATA = [] + for i in entry: + judul = i.find_all("a")[0].text + link = i.find_all("a")[0]["href"] + DATA.append({"judul": judul, "link": link}) + if not DATA: + return await msg.edit("Oops, data film tidak ditemukan di GoMov") + res = "".join(f"Judul: {i['judul']}\n{i['link']}\n\n" for i in DATA) + await msg.edit( + f"Hasil Pencarian di website GoMov:\n{res}\nScraped by @MissKatyRoBot" + ) + except Exception: + exc = traceback.format_exc() + await msg.edit(f"ERROR: {exc}") + + +@app.on_message( + filters.command( + ["savefilm21_scrap", "savefilm21_scrap@MissKatyRoBot"], COMMAND_HANDLER + ) +) +@capture_err +async def savefilm21_scrap(_, message): + try: + link = message.text.split(" ", maxsplit=1)[1] + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(link, headers=headers, follow_redirects=False) + soup = BeautifulSoup(html.text, "lxml") + res = soup.find_all(class_="button button-shadow") + res = "".join(f"{i.text}\n{i['href']}\n\n" for i in res) + await message.reply(f"Hasil Scrap dari {link}:\n\n{res}") + except IndexError: + return await message.reply( + "Gunakan command /savefilm21_scrap [link] untuk scrap link download" + ) + except Exception as e: + await message.reply(f"ERROR: {str(e)}") + + +@app.on_message( + filters.command(["nodrakor_scrap", "nodrakor_scrap@MissKatyRoBot"], COMMAND_HANDLER) +) +@capture_err +async def nodrakor_scrap(_, message): + try: + link = message.text.split(" ", maxsplit=1)[1] + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(link, headers=headers, follow_redirects=False) + soup = BeautifulSoup(html.text, "lxml") + hasil = soup.find_all(class_="gmr-download-wrap clearfix")[0] + await message.reply(f"Hasil Scrap dari {link}:\n{hasil}") + except IndexError: + return await message.reply( + "Gunakan command /nodrakor_scrap [link] untuk scrap link download" + ) + except Exception as e: + await message.reply(f"ERROR: {str(e)}") + + +# Scrape Link Download Movieku.CC +@app.on_message( + filters.command(["movieku_scrap", "movieku_scrap@MissKatyRoBot"], COMMAND_HANDLER) +) +@capture_err +async def muviku_scrap(_, message): + try: + link = message.text.split(" ", maxsplit=1)[1] + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(link, headers=headers) + soup = BeautifulSoup(html.text, "lxml") + res = soup.find_all(class_="smokeurl") + data = [] + for i in res: + for b in range(len(i.find_all("a"))): + link = i.find_all("a")[b]["href"] + kualitas = i.find_all("a")[b].text + # print(f"{kualitas}\n{link + data.append({"link": link, "kualitas": kualitas}) + if not data: + return await message.reply("Oops, data film tidak ditemukan.") + res = "".join(f"Host: {i['kualitas']}\n{i['link']}\n\n" for i in data) + await message.reply(res) + except IndexError: + return await message.reply( + "Gunakan command /movieku_scrap [link] untuk scrap link download" + ) + except Exception as e: + await message.reply(f"ERROR: {str(e)}") + + +@app.on_message( + filters.command(["melong", "melong@MissKatyRoBot"], COMMAND_HANDLER) + & filters.user([617426792, 1985689491, 1172699512, 2024984460]) +) +@capture_err +async def melong_scrap(_, message): + try: + link = message.text.split(" ", maxsplit=1)[1] + headers = { + "User-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582" + } + + html = await http.get(link, headers=headers) + soup = BeautifulSoup(html.text, "lxml") + for ep in soup.findAll(text=re.compile(r"(?i)episode\s+\d+|LINK DOWNLOAD")): + hardsub = ep.findPrevious("div") + softsub = ep.findNext("div") + rep = f"{hardsub}\n{softsub}" + await message.reply(rep) + except IndexError: + await message.reply( + "Gunakan command /melong [link] untuk scrap link download" + ) diff --git a/misskaty/plugins/sed.py b/misskaty/plugins/sed.py new file mode 100644 index 00000000..cc623873 --- /dev/null +++ b/misskaty/plugins/sed.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2018-2022 Amano Team + +import html + +import regex +from pyrogram import filters +from pyrogram.types import Message +from pyrogram.errors import MessageEmpty +from misskaty import app + + +@app.on_message(filters.regex(r"^s/(.+)?/(.+)?(/.+)?") & filters.reply) +async def sed(c: app, m: Message): + exp = regex.split(r"(? 3 else "" + + rflags = 0 + + count = 0 if "g" in flags else 1 + if "i" in flags and "s" in flags: + rflags = regex.I | regex.S + elif "i" in flags: + rflags = regex.I + elif "s" in flags: + rflags = regex.S + + text = m.reply_to_message.text or m.reply_to_message.caption + + if not text: + return + + try: + res = regex.sub( + pattern, replace_with, text, count=count, flags=rflags, timeout=1 + ) + except TimeoutError: + return await m.reply_text("Oops, your regex pattern has run for too long.") + except regex.error as e: + return await m.reply_text(str(e)) + else: + try: + await c.send_message( + m.chat.id, + f"
{html.escape(res)}
", + reply_to_message_id=m.reply_to_message.id, + ) + except MessageEmpty: + return await m.reply_text("Please reply message to use this feature.") + except Exception as e: + return await m.reply_text(f"ERROR: {str(e)}") diff --git a/misskaty/plugins/sub_extractor.py b/misskaty/plugins/sub_extractor.py new file mode 100644 index 00000000..fed0e65b --- /dev/null +++ b/misskaty/plugins/sub_extractor.py @@ -0,0 +1,150 @@ +from misskaty import app +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from misskaty.vars import COMMAND_HANDLER +from misskaty.core.decorator.errors import capture_err +from misskaty.plugins.dev import shell_exec +import json, os, traceback +from time import perf_counter +from re import split as ngesplit, I +from urllib.parse import unquote +from misskaty.helper.tools import get_random_string + +ARCH_EXT = [".mkv", ".avi", ".mp4", ".mov"] + +__MODULE__ = "MediaExtract" +__HELP__ = """ +/extractmedia [URL] - Extract subtitle or audio from video using link. +""" + + +def get_base_name(orig_path: str): + if ext := [ext for ext in ARCH_EXT if orig_path.lower().endswith(ext)]: + ext = ext[0] + return ngesplit(f"{ext}$", orig_path, maxsplit=1, flags=I)[0] + + +def get_subname(url, format): + fragment_removed = url.split("#")[0] # keep to left of first # + query_string_removed = fragment_removed.split("?")[0] + scheme_removed = query_string_removed.split("://")[-1].split(":")[-1] + if scheme_removed.find("/") == -1: + return f"MissKatySub_{get_random_string(4)}.{format}" + return f"{get_base_name(os.path.basename(unquote(scheme_removed)))}.{format}" + + +@app.on_message(filters.command(["ceksub", "extractmedia"], COMMAND_HANDLER)) +async def ceksub(_, m): + cmd = m.text.split(" ", 1) + if len(cmd) == 1: + return await m.reply( + f"Gunakan command /{m.command[0]} [link] untuk mengecek subtitle dan audio didalam video." + ) + link = cmd[1] + start_time = perf_counter() + pesan = await m.reply("Sedang memproses perintah..", quote=True) + try: + res = ( + await shell_exec( + f"ffprobe -loglevel 0 -print_format json -show_format -show_streams {link}" + ) + )[0] + details = json.loads(res) + buttons = [] + for stream in details["streams"]: + mapping = stream["index"] + try: + stream_name = stream["codec_name"] + except: + stream_name = "-" + stream_type = stream["codec_type"] + if stream_type not in ("audio", "subtitle"): + continue + try: + lang = stream["tags"]["language"] + except: + lang = mapping + buttons.append( + [ + InlineKeyboardButton( + f"0:{mapping}({lang}): {stream_type}: {stream_name}", + f"streamextract_{mapping}_{stream_name}", + ) + ] + ) + end_time = perf_counter() + timelog = "{:.2f}".format(end_time - start_time) + " second" + buttons.append([InlineKeyboardButton("Cancel", "cancel")]) + await pesan.edit( + f"Press the button below to extract subtitles/audio. Only support direct link at this time.\nProcessed in {timelog}", + reply_markup=InlineKeyboardMarkup(buttons), + ) + except Exception: + err = traceback.format_exc() + await pesan.edit(f"Gagal extract media data.\nLink: {link}\nERROR: {err}") + + +ALLOWED_USER = [978550890, 617426792, 2024984460, 1533008300, 1985689491] + + +@app.on_message(filters.command(["converttosrt"], COMMAND_HANDLER)) +@capture_err +async def convertsrt(_, m): + reply = m.reply_to_message + if not reply and reply.document: + return await m.reply( + f"Gunakan command /{m.command[0]} dengan mereply ke file ass untuk convert subtitle ke srt." + ) + msg = await m.reply("Sedang memproses perintah...") + dl = await reply.download() + (await shell_exec(f"mediaextract -i {dl} {os.path.basename(dl)}.srt"))[0] + await m.reply_document( + f"{os.path.basename(dl)}.srt", caption=f"{os.path.basename(dl)}.srt" + ) + await msg.delete() + try: + os.remove(dl) + os.remove(f"{os.path.basename(dl)}.srt") + except: + pass + + +@app.on_callback_query(filters.regex(r"^streamextract_")) +async def stream_extract(bot, update): + cb_data = update.data + usr = update.message.reply_to_message + if update.from_user.id != usr.from_user.id: + return await update.answer("⚠️ Access Denied!", True) + _, map, codec = cb_data.split("_") + link = update.message.reply_to_message.command[1] + await update.message.edit("Processing...") + if codec == "aac": + format = "aac" + elif codec == "mp3": + format = "mp3" + elif codec == "eac3": + format = "eac3" + elif codec == "subrip": + format = "srt" + else: + format == "ass" + try: + start_time = perf_counter() + namafile = get_subname(link, format) + extract = (await shell_exec(f"mediaextract -i {link} -map 0:{map} {namafile}"))[ + 0 + ] + end_time = perf_counter() + timelog = "{:.2f}".format(end_time - start_time) + " second" + await update.message.reply_document( + namafile, + caption=f"Filename: {namafile}\n\nExtracted by @MissKatyRoBot in {timelog}", + reply_to_message_id=usr.id, + ) + await update.message.delete() + try: + os.remove(namafile) + except: + pass + except Exception as e: + await update.message.edit(f"Failed extract sub. \n\nERROR: {e}") diff --git a/misskaty/plugins/tes_session.py b/misskaty/plugins/tes_session.py new file mode 100644 index 00000000..aa44d109 --- /dev/null +++ b/misskaty/plugins/tes_session.py @@ -0,0 +1,15 @@ +# This plugin to learn session using pyrogram +from misskaty import app +from misskaty.vars import COMMAND_HANDLER +from pyrogram import filters + + +@app.on_message(filters.command(["session"], COMMAND_HANDLER)) +async def session(_, message): + nama = await app.ask(message.chat.id, "Ketik nama kamu:") + umur = await app.ask(message.chat.id, "Ketik umur kamu") + alamat = await app.ask(message.chat.id, "Ketik alamat kamu:") + await app.send_message( + message.chat.id, + f"Nama Kamu Adalah: {nama.text}\nUmur Kamu Adalah: {umur.text}\nAlamat Kamu Adalah: {alamat.text}", + ) diff --git a/misskaty/plugins/ubot_plugin.py b/misskaty/plugins/ubot_plugin.py new file mode 100644 index 00000000..4a037a52 --- /dev/null +++ b/misskaty/plugins/ubot_plugin.py @@ -0,0 +1,180 @@ +import os +from pyrogram import enums, filters +from pyrogram.types import ( + Message, + InlineKeyboardMarkup, + InlineKeyboardButton, + ChatEventFilter, +) +from pyrogram.raw import functions +from misskaty import user, app +from datetime import datetime + +f = filters.chat([]) + + +@user.on_message(f) +async def auto_read(_, message: Message): + await user.read_history(message.chat.id) + await message.continue_propagation() + + +@user.on_message(filters.command("autoscroll", "!") & filters.me) +async def add_keep(_, message: Message): + if message.chat.id in f: + f.remove(message.chat.id) + await message.edit("Autoscroll dimatikan") + else: + f.add(message.chat.id) + await message.edit("Autoscroll diaktifkan, semua chat akan otomatis terbaca") + + +# @user.on_deleted_messages(filters.chat([-1001455886928, -1001255283935])) +async def del_msg(client, message): + async for a in user.get_chat_event_log( + message[0].chat.id, limit=1, filters=ChatEventFilter(deleted_messages=True) + ): + try: + ustat = ( + await user.get_chat_member( + message[0].chat.id, a.deleted_message.from_user.id + ) + ).status + except: + ustat = enums.ChatMemberStatus.MEMBER + if ( + ustat + in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER] + or a.deleted_message.from_user.is_bot + ): + return + if a.user.id == a.deleted_message.from_user.id: + if a.deleted_message.text: + await app.send_message( + a.deleted_message.chat.id, + f"#DELETED_MESSAGE\n\n{a.deleted_message.from_user.first_name} menghapus pesannya 🧐.\nPesan: {a.deleted_message.text}", + ) + elif a.deleted_message.video: + await app.send_message( + a.deleted_message.chat.id, + f"#DELETED_MESSAGE\n\n{a.deleted_message.from_user.first_name} menghapus pesannya 🧐.\nNama file: {a.deleted_message.video.file_name}", + ) + + +# @user.on_edited_message(filters.text & filters.chat(-1001455886928)) +async def edit_msg(client, message): + try: + ustat = ( + await user.get_chat_member(message.chat.id, message.from_user.id) + ).status + except: + ustat = enums.ChatMemberStatus.MEMBER + if message.from_user.is_bot or ustat in [ + enums.ChatMemberStatus.ADMINISTRATOR, + enums.ChatMemberStatus.OWNER, + ]: + return + async for a in user.get_chat_event_log( + message.chat.id, limit=1, filters=ChatEventFilter(edited_messages=True) + ): + if a.old_message.text.startswith( + ("/mirror", "/leech", "/unzipmirror", "/unzipleech") + ): + await app.send_message( + message.chat.id, + f"#EDITED_MESSAGE\n\n{a.user.first_name} mengedit pesannya 🧐.\nPesan: {a.old_message.text}", + ) + + +@user.on_message(filters.private & ~filters.bot & ~filters.me & filters.text) +async def message_pm(client, message): + await app.send_message( + 617426792, + f"Ada pesan baru dari {message.from_user.mention}\n\nPesan: {message.text}", + ) + + +@user.on_message(~filters.bot & filters.group & filters.mentioned) +async def mentioned(client, message): + if message.sender_chat: + return + cid = message.chat.id + pesan = message.text or message.caption + await app.send_message( + 617426792, + f"{message.from_user.mention} mention kamu di {message.chat.title}\n\nPesan: {pesan}", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="💬 Lihat Pesan", + url=f"https://t.me/c/{str(cid)[4:]}/{message.id}", + ) + ] + ] + ), + ) + + +@user.on_message(filters.command("joindate", "!") & filters.me) +async def join_date(app, message: Message): + members = [] + async for m in app.iter_chat_members(message.chat.id): + members.append( + ( + m.user.first_name, + m.joined_date or (await app.get_messages(message.chat.id, 1)).date, + ) + ) + members.sort(key=lambda member: member[1]) + + with open("joined_date.txt", "w", encoding="utf8") as f: + f.write("Join Date First Name\n") + for member in members: + f.write( + str(datetime.fromtimestamp(member[1]).strftime("%y-%m-%d %H:%M")) + + f" {member[0]}\n" + ) + + await user.send_document(message.chat.id, "joined_date.txt") + os.remove("joined_date.txt") + + +@user.on_message(filters.command("memberstats", "!") & filters.me) +async def memberstats(client, message): + people = {} + total = await user.get_chat_members_count(message.chat.id) + async for msg in user.iter_history(message.chat.id, limit=1000): + if msg.from_user and not msg.from_user.is_bot: + people[msg.from_user.id] = msg.from_user.first_name + await message.edit(f"{round(len(people) / total)}%") + + +@user.on_message(filters.command("recent_action", "!") & filters.me) +async def recent_act(client, message): + full_log = await user.invoke( + functions.channels.GetAdminLog( + channel=await user.resolve_peer(message.chat.id), + q="", + max_id=0, + min_id=0, + limit=0, + ) + ) + with open( + f"recent_actions_{message.chat.id}.txt", "w", encoding="utf8" + ) as log_file: + log_file.write(str(full_log)) + await message.reply_document(f"recent_actions_{message.chat.id}.txt") + + +@user.on_message(filters.command(["screenshot"], prefixes="!")) +async def take_a_screenshot(client, message): + await message.delete() + await user.invoke( + functions.messages.SendScreenshotNotification( + peer=await user.resolve_peer(message.chat.id), + reply_to_msg_id=0, + random_id=app.rnd_id(), + ) + ) diff --git a/misskaty/plugins/webss.py b/misskaty/plugins/webss.py new file mode 100644 index 00000000..8248d63b --- /dev/null +++ b/misskaty/plugins/webss.py @@ -0,0 +1,76 @@ +from asyncio import gather +from base64 import b64decode +from io import BytesIO +from pyrogram import filters +from misskaty import app +from misskaty.vars import COMMAND_HANDLER +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.http import post + +__MODULE__ = "WebSS" +__HELP__ = """ +/webss [URL] [FULL_SIZE?, use (y|yes|true) to get full size image. (optional)] - Take A Screenshot Of A Webpage. +""" + + +async def take_screenshot(url: str, full: bool = False): + url = url if url.startswith("http") else f"https://{url}" + payload = { + "url": url, + "width": 1920, + "height": 1080, + "scale": 1, + "format": "jpeg", + } + if full: + payload["full"] = True + data = await post( + "https://webscreenshot.vercel.app/api", + data=payload, + ) + if "image" not in data: + return None + b = data["image"].replace("data:image/jpeg;base64,", "") + file = BytesIO(b64decode(b)) + file.name = "webss.jpg" + return file + + +@app.on_message(filters.command(["webss"], COMMAND_HANDLER)) +@capture_err +async def take_ss(_, message): + if len(message.command) < 2: + return await message.reply("Give A Url To Fetch Screenshot.") + + if len(message.command) == 2: + url = message.text.split(None, 1)[1] + full = False + elif len(message.command) == 3: + url = message.text.split(None, 2)[1] + full = message.text.split(None, 2)[2].lower().strip() in [ + "yes", + "y", + "1", + "true", + ] + else: + return await message.reply("Invalid Command.") + + m = await message.reply("Capturing screenshot...") + + try: + photo = await take_screenshot(url, full) + if not photo: + return await m.edit("Failed To Take Screenshot") + + m = await m.edit("Uploading...") + + if not full: + # Full size images have problem with reply_photo, that's why + # we need to only use reply_photo if we're not using full size + await gather(*[message.reply_document(photo), message.reply_photo(photo)]) + else: + await message.reply_document(photo) + await m.delete() + except Exception as e: + await m.edit(str(e)) diff --git a/misskaty/plugins/ytdl_download.py b/misskaty/plugins/ytdl_download.py new file mode 100644 index 00000000..04774454 --- /dev/null +++ b/misskaty/plugins/ytdl_download.py @@ -0,0 +1,386 @@ +""" + * @author yasir + * @date 2022-12-01 09:12:27 + * @lastModified 2022-12-01 09:32:31 + * @projectName MissKatyPyro + * Copyright @YasirPedia All rights reserved + """ +import os, logging, json, shutil, asyncio, time +from misskaty import app +from PIL import Image +from pyrogram import filters +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from misskaty.vars import COMMAND_HANDLER +from datetime import datetime +from hachoir.metadata import extractMetadata +from hachoir.parser import createParser +from misskaty.helper.ytdl_helper import random_char, DownLoadFile +from misskaty.helper.human_read import get_readable_file_size +from misskaty.plugins.dev import shell_exec +from misskaty.core.decorator.errors import capture_err +from misskaty.helper.pyro_progress import progress_for_pyrogram + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) +LOGGER = logging.getLogger(__name__) + +user_time = {} + + +@app.on_message(filters.command(["ytdown"], COMMAND_HANDLER) & ~filters.channel) +@capture_err +async def ytdown(_, message): + if len(message.command) == 1: + return await message.reply( + f"Gunakan command /{message.command[0]} YT_LINK untuk download video dengan YT-DLP." + ) + userLastDownloadTime = user_time.get(message.chat.id) + try: + if userLastDownloadTime > datetime.now(): + wait_time = round( + (userLastDownloadTime - datetime.now()).total_seconds() / 60, 2 + ) + await message.reply_text(f"Wait {wait_time} Minutes before next request..") + return + except: + pass + + url = message.command[1] + command_to_exec = f"yt-dlp --no-warnings --youtube-skip-dash-manifest -j {url}" + t_response = (await shell_exec(command_to_exec))[0] + if "ERROR" in t_response: + await message.reply_text(t_response, quote=True, disable_web_page_preview=True) + return False + inline_keyboard = [] + if t_response: + x_reponse = t_response + if "\n" in x_reponse: + x_reponse, _ = x_reponse.split("\n") + response_json = json.loads(x_reponse) + randem = random_char(5) + if not os.path.exists("./YT_Down"): + os.makedirs("./YT_Down") + save_ytdl_json_path = f"YT_Down/{str(message.from_user.id)}{randem}.json" + with open(save_ytdl_json_path, "w", encoding="utf8") as outfile: + json.dump(response_json, outfile, ensure_ascii=False) + duration = None + if "duration" in response_json: + duration = response_json["duration"] + if "formats" in response_json: + for formats in response_json["formats"]: + format_id = formats.get("format_id") + format_string = formats.get("format_note") + if format_string in ["ultralow", "low", "medium"]: + continue + if format_string is None: + format_string = formats.get("format") + format_ext = formats.get("ext") + if format_ext == "mhtml": + continue + if formats.get("filesize"): + size = formats["filesize"] + elif formats.get("filesize_approx"): + size = formats["filesize_approx"] + else: + size = 0 + cb_string_video = f"ytdl|video|{format_id}|{format_ext}|{randem}" + cb_string_file = f"ytdl|file|{format_id}|{format_ext}|{randem}" + if format_string and "audio only" not in format_string: + ikeyboard = [ + InlineKeyboardButton( + f"🎬 {format_string} {format_ext} {get_readable_file_size(size)} ", + callback_data=(cb_string_video).encode("UTF-8"), + ), + InlineKeyboardButton( + f"📄 {format_string} {format_ext} {get_readable_file_size(size)} ", + callback_data=(cb_string_file).encode("UTF-8"), + ), + ] + + else: + # special weird case :\ + ikeyboard = [ + InlineKeyboardButton( + "SVideo [" + "] ( " + get_readable_file_size(size) + " )", + callback_data=(cb_string_video).encode("UTF-8"), + ), + InlineKeyboardButton( + "DFile [" + "] ( " + get_readable_file_size(size) + " )", + callback_data=(cb_string_file).encode("UTF-8"), + ), + ] + inline_keyboard.append(ikeyboard) + if duration is not None: + cb_string_64 = f"ytdl|audio|64k|mp3|{randem}" + cb_string_128 = f"ytdl|audio|128k|mp3|{randem}" + cb_string = f"ytdl|audio|320k|mp3|{randem}" + inline_keyboard.extend( + ( + [ + InlineKeyboardButton( + "MP3 " + "(" + "64 kbps" + ")", + callback_data=cb_string_64.encode("UTF-8"), + ), + InlineKeyboardButton( + "MP3 " + "(" + "128 kbps" + ")", + callback_data=cb_string_128.encode("UTF-8"), + ), + ], + [ + InlineKeyboardButton( + "MP3 " + "(" + "320 kbps" + ")", + callback_data=cb_string.encode("UTF-8"), + ) + ], + ) + ) + + else: + format_id = response_json["format_id"] + format_ext = response_json["ext"] + cb_string_file = f"ytdl|file|{format_id}|{format_ext}|{randem}" + cb_string_video = f'ytdl|{"video"}|{format_id}|{format_ext}|{randem}' + inline_keyboard.append( + [ + InlineKeyboardButton( + "SVideo", callback_data=(cb_string_video).encode("UTF-8") + ), + InlineKeyboardButton( + "DFile", callback_data=(cb_string_file).encode("UTF-8") + ), + ] + ) + cb_string_file = f'{"file"}={format_id}={format_ext}' + cb_string_video = f'{"video"}={format_id}={format_ext}' + inline_keyboard.append( + [ + InlineKeyboardButton( + "video", callback_data=(cb_string_video).encode("UTF-8") + ), + InlineKeyboardButton( + "file", callback_data=(cb_string_file).encode("UTF-8") + ), + ] + ) + reply_markup = InlineKeyboardMarkup(inline_keyboard) + thumbnail = "https://uxwing.com/wp-content/themes/uxwing/download/signs-and-symbols/no-video-icon.png" + thumbnail_image = "https://uxwing.com/wp-content/themes/uxwing/download/signs-and-symbols/no-video-icon.png" + if "thumbnail" in response_json and response_json["thumbnail"] is not None: + thumbnail = response_json["thumbnail"] + thumbnail_image = response_json["thumbnail"] + thumb_image_path = DownLoadFile( + thumbnail_image, + f"YT_Down/{str(message.from_user.id)}{randem}.jpg", + 128, + None, + "Trying to download..", + message.id, + message.chat.id, + ) # bot, + await message.reply_photo( + photo=thumb_image_path, + quote=True, + caption=f"Select the desired format: file size might be approximate", + reply_markup=reply_markup, + ) + + else: + cb_string_file = f'{"file"}={"LFO"}={"NONE"}' + cb_string_video = f'{"video"}={"OFL"}={"ENON"}' + inline_keyboard.append( + [ + InlineKeyboardButton( + "SVideo", callback_data=(cb_string_video).encode("UTF-8") + ), + InlineKeyboardButton( + "DFile", callback_data=(cb_string_file).encode("UTF-8") + ), + ] + ) + reply_markup = InlineKeyboardMarkup(inline_keyboard) + await message.reply_photo( + photo="https://telegra.ph/file/ce37f8203e1903feed544.png", + quote=True, + caption=f"""Select the desired format: file size might be approximate""", + reply_markup=reply_markup, + reply_to_message_id=message.id, + ) + + +@app.on_callback_query(filters.regex(r"^ytdl|")) +async def youtube_dl_call_back(bot, update): + cb_data = update.data + usr = update.message.reply_to_message + if update.from_user.id != usr.from_user.id: + return await update.answer("⚠️ Akses Denied!", True) + # youtube_dl extractors + _, tg_send_type, youtube_dl_format, youtube_dl_ext, ranom = cb_data.split("|") + thumb_image_path = f"YT_Down/{str(update.from_user.id)}{ranom}.jpg" + save_ytdl_json_path = f"YT_Down/{str(update.from_user.id)}{ranom}.json" + try: + with open(save_ytdl_json_path, "r", encoding="utf8") as f: + response_json = json.load(f) + except FileNotFoundError: + await update.message.delete() + return False + + custom_file_name = ( + f"{str(response_json.get('title'))}_{youtube_dl_format}.{youtube_dl_ext}" + ) + custom_file_name = "%(title,fulltitle,alt_title)s %(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s" + youtube_dl_url = update.message.reply_to_message.text.split(" ", 1)[1] + await update.message.edit_caption("Trying to download media...") + tmp_directory_for_each_user = os.path.join( + f"downloads/{str(update.from_user.id)}{random_char(5)}" + ) + if not os.path.isdir(tmp_directory_for_each_user): + os.makedirs(tmp_directory_for_each_user) + download_directory = os.path.join(tmp_directory_for_each_user, custom_file_name) + if tg_send_type == "audio": + command_to_exec = f"yt-dlp -c --ffmpeg-location '/usr/bin/mediaextract' --max-filesize 2097152000 --prefer-ffmpeg --extract-audio --embed-metadata --audio-format {youtube_dl_ext} --audio-quality {youtube_dl_format} {youtube_dl_url} --output '{download_directory}'" + else: + minus_f_format = youtube_dl_format + if "youtu" in youtube_dl_url: + minus_f_format = f"{youtube_dl_format}+bestaudio[ext=m4a]" + command_to_exec = f"yt-dlp -c --ffmpeg-location '/usr/bin/mediaextract' --max-filesize 2097152000 --embed-subs --embed-metadata -f {minus_f_format} --hls-prefer-ffmpeg {youtube_dl_url} --output '{download_directory}'" + start = datetime.now() + t_response = (await shell_exec(command_to_exec))[0] + if t_response: + os.remove(save_ytdl_json_path) + end_one = datetime.now() + time_taken_for_download = (end_one - start).seconds + file_size = 2097152000 + 1 + download_directory_dirname = os.path.dirname(download_directory) + download_directory_contents = os.listdir(download_directory_dirname) + for download_directory_c in download_directory_contents: + current_file_name = os.path.join( + download_directory_dirname, download_directory_c + ) + file_size = os.stat(current_file_name).st_size + + if file_size == 0: + await update.message.edit(text="File Not found 🤒") + asyncio.create_task(clendir(tmp_directory_for_each_user)) + return + + if file_size > 2097152000: + await update.message.edit_caption( + caption="I cannot upload files greater than 1.95GB due to Telegram API limitations. This file has size {}.".format( + get_readable_file_size(file_size) + ) + ) + asyncio.create_task(clendir(thumb_image_path)) + asyncio.create_task(clendir(tmp_directory_for_each_user)) + return + + else: + await update.message.edit_caption(caption="Trying to upload..") + # get the correct width, height, and duration + # for videos greater than 10MB + # ref: message from @BotSupport + width = 0 + height = 0 + duration = 0 + if tg_send_type != "file": + metadata = extractMetadata(createParser(current_file_name)) + if metadata is not None and metadata.has("duration"): + duration = metadata.get("duration").seconds + # get the correct width, height, and duration + # for videos greater than 10MB + if os.path.exists(thumb_image_path): + # https://stackoverflow.com/a/21669827/4723940 + Image.open(thumb_image_path).convert("RGB").save(thumb_image_path) + metadata = extractMetadata(createParser(thumb_image_path)) + if metadata.has("width"): + width = metadata.get("width") + if metadata.has("height"): + height = metadata.get("height") + if tg_send_type == "vm": + height = width + else: + thumb_image_path = None + start_time = time.time() + # try to upload file + if tg_send_type == "audio": + await update.message.reply_audio( + audio=current_file_name, + caption=f"{os.path.basename(current_file_name)}", + duration=duration, + thumb=thumb_image_path, + reply_to_message_id=usr.id, + progress=progress_for_pyrogram, + progress_args=( + "Trying to upload...", + update.message, + start_time, + ), + ) + elif tg_send_type == "file": + await update.message.reply_document( + document=current_file_name, + thumb=thumb_image_path, + caption=f"{os.path.basename(current_file_name)}", + reply_to_message_id=usr.id, + progress=progress_for_pyrogram, + progress_args=( + "Trying to upload...", + update.message, + start_time, + ), + ) + elif tg_send_type == "vm": + await update.message.reply_video_note( + video_note=current_file_name, + duration=duration, + length=width, + thumb=thumb_image_path, + reply_to_message_id=usr.id, + progress=progress_for_pyrogram, + progress_args=( + "Trying to upload...", + update.message, + start_time, + ), + ) + elif tg_send_type == "video": + await update.message.reply_video( + video=current_file_name, + caption=f"{os.path.basename(current_file_name)}", + duration=duration, + width=width, + height=height, + supports_streaming=True, + reply_to_message_id=usr.id, + thumb=thumb_image_path, + progress=progress_for_pyrogram, + progress_args=( + "Trying to upload...", + update.message, + start_time, + ), + ) + else: + LOGGER.info("Did this happen? :\\") + end_two = datetime.now() + time_taken_for_upload = (end_two - end_one).seconds + await update.message.edit_caption( + caption="Downloaded in {} seconds.\nUploaded in {} seconds.".format( + time_taken_for_download, time_taken_for_upload + ) + ) + asyncio.create_task(clendir(thumb_image_path)) + asyncio.create_task(clendir(tmp_directory_for_each_user)) + await asyncio.sleep(5) + await update.message.delete() + + +async def clendir(directory): + try: + shutil.rmtree(directory) + except: + pass + try: + os.remove(directory) + except: + pass diff --git a/misskaty/vars.py b/misskaty/vars.py new file mode 100644 index 00000000..319f7cd8 --- /dev/null +++ b/misskaty/vars.py @@ -0,0 +1,69 @@ +import logging +from os import environ +from dotenv import load_dotenv + +load_dotenv("config.env", override=True) + +LOGGER = logging.getLogger(__name__) + + +def getConfig(name: str): + try: + return environ[name] + except: + return "" + + +# Required ENV +try: + BOT_TOKEN = getConfig("BOT_TOKEN") + API_ID = getConfig("API_ID") + API_HASH = getConfig("API_HASH") + # MongoDB information + DATABASE_URI = getConfig("DATABASE_URI") + DATABASE_NAME = getConfig("DATABASE_NAME") + LOG_CHANNEL = int(getConfig("LOG_CHANNEL")) + USER_SESSION = getConfig("USER_SESSION") +except Exception as e: + LOGGER.error(f"One or more env variables missing! Exiting now.\n{e}") + exit(1) +COMMAND_HANDLER = environ.get("COMMAND_HANDLER", "! /").split() +SUDO = list( + { + int(x) + for x in environ.get( + "SUDO", + "617426792 2024984460", + ).split() + } +) +SUPPORT_CHAT = environ.get("SUPPORT_CHAT", "YasirPediaChannel") + +## Config For AUtoForwarder +# Forward From Chat ID +FORWARD_FROM_CHAT_ID = list( + { + int(x) + for x in environ.get( + "FORWARD_FROM_CHAT_ID", + "-1001128045651 -1001455886928 -1001686184174", + ).split() + } +) +# Forward To Chat ID +FORWARD_TO_CHAT_ID = list( + {int(x) for x in environ.get("FORWARD_TO_CHAT_ID", "-1001210537567").split()} +) +FORWARD_FILTERS = list(set(environ.get("FORWARD_FILTERS", "video document").split())) +BLOCK_FILES_WITHOUT_EXTENSIONS = bool( + environ.get("BLOCK_FILES_WITHOUT_EXTENSIONS", True) +) +BLOCKED_EXTENSIONS = list( + set( + environ.get( + "BLOCKED_EXTENSIONS", + "html htm json txt php gif png ink torrent url nfo xml xhtml jpg", + ).split() + ) +) +MINIMUM_FILE_SIZE = environ.get("MINIMUM_FILE_SIZE", None) diff --git a/utils.py b/utils.py new file mode 100644 index 00000000..62e85627 --- /dev/null +++ b/utils.py @@ -0,0 +1,100 @@ +import logging +from pyrogram.errors import FloodWait, InputUserDeactivated, PeerIdInvalid, UserIsBlocked +import asyncio +from pyrogram.types import Message +from typing import Union +import os +import emoji +from database.users_chats_db import db + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +BANNED = {} + + +# temp db for banned +class temp(object): + BANNED_USERS = [] + BANNED_CHATS = [] + ME = None + CURRENT = int(os.environ.get("SKIP", 2)) + CANCEL = False + MELCOW = {} + U_NAME = None + B_NAME = None + + +def demoji(teks): + return emoji.emojize(f":{teks.replace(' ', '_').replace('-', '_')}:") + + +async def broadcast_messages(user_id, message): + try: + await message.copy(chat_id=user_id) + return True, "Succes" + except FloodWait as e: + await asyncio.sleep(e.x) + return await broadcast_messages(user_id, message) + except InputUserDeactivated: + await db.delete_user(int(user_id)) + logging.info(f"{user_id}-Removed from Database, since deleted account.") + return False, "Deleted" + except UserIsBlocked: + logging.info(f"{user_id} -Blocked the bot.") + return False, "Blocked" + except PeerIdInvalid: + await db.delete_user(int(user_id)) + logging.info(f"{user_id} - PeerIdInvalid") + return False, "Error" + except Exception: + return False, "Error" + + +def get_size(size): + """Get size in readable format""" + + units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"] + size = float(size) + i = 0 + while size >= 1024.0 and i < len(units): + i += 1 + size /= 1024.0 + return "%.2f %s" % (size, units[i]) + + +def get_file_id(msg: Message): + if msg.media: + for message_type in ("photo", "animation", "audio", "document", "video", "video_note", "voice", "sticker"): + if obj := getattr(msg, message_type): + setattr(obj, "message_type", message_type) + return obj + + +def extract_user(message: Message) -> Union[int, str]: + """extracts the user from a message""" + # https://github.com/SpEcHiDe/PyroGramBot/blob/f30e2cca12002121bad1982f68cd0ff9814ce027/pyrobot/helper_functions/extract_user.py#L7 + user_id = None + user_first_name = None + if message.reply_to_message: + user_id = message.reply_to_message.from_user.id + user_first_name = message.reply_to_message.from_user.first_name + + elif len(message.command) > 1: + if len(message.entities) > 1 and message.entities[1].type == "text_mention": + + required_entity = message.entities[1] + user_id = required_entity.user.id + user_first_name = required_entity.user.first_name + else: + user_id = message.command[1] + # don't want to make a request -_- + user_first_name = user_id + try: + user_id = int(user_id) + except ValueError: + pass + else: + user_id = message.from_user.id + user_first_name = message.from_user.first_name + return (user_id, user_first_name)