diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..11d96d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5a6ea7d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[jayantkageri@gmail.com](mailto:jayantkageri@gmail.com). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/README.md b/README.md index 4c94d78..38a047c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ from pyrogram import Client app = tgClient(Client("my_account")) -@command("start", group_only=True) +@app.command("start", group_only=True) async def start(client, message): await message.reply_text(f"Hello {message.from_user.mention}") @@ -54,9 +54,6 @@ app.run() ## Configuration Make an .env or set the Following in your Environment Variables. - - `API_ID` - The API ID Provided by Telegram - - `API_HASH` - The API Hash Provided by Telegram - - `BOT_TOKEN` - Bot Token provided by Bot Father - `LOGS` - Log Group ID - `PLUGINS` - Plugins Directory Path where your Plugins are located, By Default it is `plugins` Directory - `HANDLERS` - The Command Handlers, By Default it is `/` and `!` @@ -77,7 +74,7 @@ from pyrogram import Client app = tgClient(Client("my_account")) ``` -### `tgEasy.command` +### `tgEasy.tgClient.command` - A decorater to Register Commands in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_message(pyrogram.filters.command('command'))` - Parameters: - command (str || list): @@ -101,14 +98,16 @@ app = tgClient(Client("my_account")) #### Example ```python import pyrogram -from tgEasy import command +from tgEasy import tgClient + +app = tgClient(pyrogram.Client()) -@command("start", group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text) +@app.command("start", group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text) async def start(client, message): await message.reply_text(f"Hello {message.from_user.mention}") ``` -### `tgEasy.callback` +### `tgEasy.tgClient.callback` - A decorater to Register Callback Quiries in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_callback_query(pyrogram.filters.regex('^data.*'))` - Parameters: @@ -124,9 +123,11 @@ async def start(client, message): #### Example ```python import pyrogram -from tgEasy import command, callback +from tgEasy import tgClient -@command("start") +app = tgClient(pyrogram.Client()) + +@app.command("start") async def start(client, message): await message.reply_text( f"Hello {message.from_user.mention}", @@ -138,11 +139,11 @@ async def start(client, message): ]]) ) -@callback("data") +@app.callback("data") async def data(client, CallbackQuery): await CallbackQuery.answer("Hello :)", show_alert=True) ``` -### `tgEasy.adminsOnly` +### `tgEasy.tgClient.adminsOnly` - A decorater for running the function only if the admin have the specified Rights. - We are still Working on this to make it to check Rights for Anonoymous Admins, Stay Tuned. @@ -155,23 +156,47 @@ async def data(client, CallbackQuery): #### Example ```python -from tgEasy import command, adminsOnly +from tgEasy import tgClient +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("start") -@adminsOnly("can_change_info") +@app.command("start") +@app.adminsOnly("can_change_info") async def start(client, message): await message.reply_text(f"Hello Admin {message.from_user.mention}") ``` ### `tgEasy.tgClient.run()` -- Runs the `pyrogram.Client` by adding `tgEasy.tgClient.run()` in your main file and run [Not Recommended to use this], instead of running `python3 -m tgEasy`. +- Runs the `pyrogram.Client` by adding `tgEasy.tgClient.run()` in your main file and run. - This calls `pyrogram.Client.start()`, `pyrogram.idle()` and `pyrogram.Client.stop()` #### Example ```python -from tgEasy import run +import pyrogram +from tgEasy import tgClient + +app = tgClient(pyrogram.Client()) + +app.run() +``` + +### `tgEast.tgClint.runClients()` +- Runs the Multiple `pyrogram.Client` of tgEasy by adding `tgEasy.tgClient.run()` in your main file and run. + +- This calls `pyrogram.Client.start()`, `pyrogram.idle()` and `pyrogram.Client.stop()` + +- Pass the tgEasy Clients in it. + +#### Example +```python +from tgEasy import tgClient +import pyrogram -run() +app = tgClient(pyrogram.Client()) +app1 = tgClient(pyrogram.Client()) + +tgClient.runClients(app, app1) ``` ### `tgEasy.get_user` - Gets a User from Message/RepliedMessageFromUser @@ -183,10 +208,13 @@ run() #### Example ```python -from tgEasy import get_user, command, adminsOnly +from tgEasy import get_user, tgClient +import pyrogram -@command("ban", group_only=True, self_admin=True) -@adminsOnly("can_restrict_members") +app = tgClient(pyrogram.Client()) + +@app.command("ban", group_only=True, self_admin=True) +@app.adminsOnly("can_restrict_members") async def ban(client, message): user = await get_user(message) await message.chat.kick_member(user.id) @@ -202,9 +230,12 @@ async def ban(client, message): #### Example ```python -from tgEasy import command, get_user_adv +from tgEasy import get_user_adv +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("id") +@app.command("id") async def id(client, message): user = await get_user_adv(message) await message.reply_text(f"Your ID is `{user.id}`") @@ -224,15 +255,21 @@ async def id(client, message): - rights (str): - The Rights have to Check. + - client (`pyrogram.Client`): + - From which Client to Check the Rights. + - Returns: - `True` if the User have the Right. - `False` if the User don't have the Right. #### Example ```python -from tgEasy import command, check_rights, get_user +from tgEasy import tgClient, check_rights, get_user +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("ban", group_only=True, self_admin=True) +@app.command("ban", group_only=True, self_admin=True) async def ban(client, message): if not await check_rights(message.chat.id, message.from_user.id, "can_restrict_members"): return await message.reply_text("You don't have necessary rights to use this Command.") @@ -249,15 +286,21 @@ async def ban(client, message): - user_id (int): - The User ID of Whose Admin Status have to Check. + - client (`pyrogram.Client`): + - From which Client to Check the Admin Status. + - Returns: - `True` if the User is Admin. - `False` if the User is't Admin. #### Example ```python -from tgEasy import command, is_admin, adminsOnly +from tgEasy import tgClient, is_admin, adminsOnly +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("ban", group_only=True, self_admin=True) -@adminsOnly("can_restrict_members") +@app.command("ban", group_only=True, self_admin=True) +@app.adminsOnly("can_restrict_members") async def ban(client, message): if await is_admin(message.chat.id, (await get_user(mesasge)).id): return await message.reply_text("You can't Ban Admins.") @@ -278,9 +321,12 @@ async def ban(client, message): #### Exapmle ```python -from tgEasy import command, handle_error +from tgEasy import tgClient, handle_error +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("start") +@app.command("start") async def start(client, message): try: await message.reply_text("Hi :D') # I intentionally made an bug for Example :/ @@ -297,9 +343,12 @@ async def start(client, message): #### Example ```python -from tgEasy import command, send_typing +from tgEasy import tgClinet, send_typing +import pyrogram + +app = tgClient(pyrogram.Client()) -@command("start") +@app.command("start") async def start(client, message): await send_typing(message) await message.reply_text("Hello") diff --git a/tgEasy/__init__.py b/tgEasy/__init__.py index cdf0279..2cba347 100644 --- a/tgEasy/__init__.py +++ b/tgEasy/__init__.py @@ -16,23 +16,39 @@ # You should have received a copy of the GNU Lesser General Public License # along with tgEasy. If not, see . +import asyncio +import logging as logger import os import typing import pyrogram -from .config import Config +from pyrogram import client from pyromod import listen -from pyromod.helpers import ikb, array_chunk, bki, btn, force_reply, kb, kbtn, ntb +from pyromod.helpers import (array_chunk, bki, btn, force_reply, ikb, kb, kbtn, + ntb) from pyromod.nav import Pagination +from .config import Config from .decorater import * from .helpers import * -import logging -__version__ = "1.1.3" + +__version__ = "1.2.3" __copyright__ = "Copyright 2021 Jayant Hegde Kageri " __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" +logging = logger.getLogger("tgEasy") +main_event_loop = asyncio.get_event_loop() + -class tgClient: +class Methods( + Command, + Callback, + AdminsOnly +): + pass + + +class tgClient(Methods, Scaffold): + notice_displayed = False """ ### `tgEasy.tgClient` - A Class for Initialising the tgEasy and it's Methods, Types and Functions @@ -48,20 +64,23 @@ class tgClient: app = tgClient(Client("my_account")) """ __client__ = None + def __init__( self, - client = pyrogram.Client + client=pyrogram.Client ): super().__init__() self.__client__ = client tgClient.__client__ = self.__client__ - print(f"tgEasy v{__version__}, {__copyright__}") - print(f"Licenced under the terms of {__license__}", end='\n\n') + if not tgClient.notice_displayed: + print(f"tgEasy v{__version__}, {__copyright__}") + print(f"Licenced under the terms of {__license__}", end='\n\n') + tgClient.notice_displayed = True def run(self): """ ### `tgEasy.tgClient.run()` - - Runs the `pyrogram.Client` by adding `tgEasy.tgClient.run()` in your main file and run. + - Runs the `pyrogram.Client` by adding `tgEasy.tgClient.run()` in your main file. - This calls `pyrogram.Client.start()`, `pyrogram.idle()` and `pyrogram.Client.stop()` #### Example @@ -75,13 +94,13 @@ def run(self): """ import logging if not Config.LOGS: - logging.warning("Log Group ID is't Set, Please set it else Bot will not able to Send Crash Logs") + logging.warning( + "Log Group ID is't Set, Please set it else Bot will not able to Send Crash Logs") logging.info("Starting the pyrogram.Client") try: self.__client__.start() - self.__client__.send_message(Config.LOGS, "pyrogram.Client Started") - except pyrogram.errors.exceptions.bad_request_400.AccessTokenExpired: - raise AccessTokenExpired("[400 ACCESS_TOKEN_EXPIRED]: The bot token has expired, Use Diffrent one or change it to the Correct one") + self.__client__.send_message( + Config.LOGS, "pyrogram.Client Started") except pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid: logging.warning("Interact the Bot to your Log Group Now") pass @@ -93,8 +112,63 @@ def run(self): self.__client__.send_message( Config.LOGS, "pyrogram.Client Stopped, If this is UnExpected check Logs") except pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid: - logging.warning("Unable to Send Message to Log Group, Please Interact Bot with the Log Group while Running") + logging.warning( + "Unable to Send Message to Log Group, Please Interact Bot with the Log Group while Running") pass logging.info("Stopping the pyrogram.Client") self.__client__.stop() - logging.info("Stopped the pyrogram.Client") \ No newline at end of file + logging.info("Stopped the pyrogram.Client") + + def runClients(*args): + """ + ### `tgEast.tgClint.runClients()` + - Runs the Multiple `pyrogram.Client` of tgEasy by adding `tgEasy.tgClient.run()` in your main file and run. + + - This calls `pyrogram.Client.start()`, `pyrogram.idle()` and `pyrogram.Client.stop()` + + - Pass the tgEasy Clients in it. + + #### Example + .. code-block:: python + from tgEasy import tgClient + import pyrogram + + app = tgClient(pyrogram.Client()) + app1 = tgClient(pyrogram.Client()) + + tgClient.runClients(app, app1) + """ + import logging + if not Config.LOGS: + logging.warning( + "Log Group ID is't Set, Please set it else Bot will not able to Send Crash Logs") + logging.info("Starting the pyrogram.Client") + try: + for clients in args: + clients.__client__.start() + try: + clients.__client__.send_message( + Config.LOGS, "pyrogram.Client Started") if Config.LOGS else None + except pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid: + logging.warning("Interact the Bot to your Log Group Now") + pass + logging.info(f"Started the {client}") + except pyrogram.errors.exceptions.bad_request_400.PeerIdInvalid: + logging.warning("Interact with your Log Group Now") + pass + + logging.info("Idling the pyrogram.Client") + pyrogram.idle() + + for clients in args: + try: + logging.info(f"Sending Message before Stopping the {client}") + clients.__client__.send_message( + Config.LOGS, "pyrogram.Client Stopped, If this is UnExpected check Logs") if Config.LOGS else None + + except: + logging.warning( + "Unable to Send Message to Log Group, Please Interact Bot with the Log Group while Running") + pass + + clients.__client__.stop() diff --git a/tgEasy/config.py b/tgEasy/config.py index 29571b1..3c80e31 100644 --- a/tgEasy/config.py +++ b/tgEasy/config.py @@ -28,9 +28,6 @@ class Config: """ Configuratoins of `tgEasy`. """ - API_ID = config("API_ID") - API_HASH = config("API_HASH") - BOT_TOKEN = config("BOT_TOKEN") LOGS = config("LOGS") PLUGINS = config("PLUGINS", default=None) - HANDLERS = list(config("HANDLER", default="/!")) \ No newline at end of file + HANDLERS = list(config("HANDLER", default="/!")) diff --git a/tgEasy/decorater.py b/tgEasy/decorater.py index dfb371a..8514b0a 100644 --- a/tgEasy/decorater.py +++ b/tgEasy/decorater.py @@ -22,178 +22,192 @@ import pyrogram from .config import Config from .helpers import * - -def command(command: typing.Union[str, list], pm_only: typing.Union[bool, bool] = False, group_only: typing.Union[bool, bool] = False, self_admin: typing.Union[bool, bool] = False, self_only: typing.Union[bool] = False, filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, *args, **kwargs): - """ -### `tgEasy.command` -- A decorater to Register Commands in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_message(pyrogram.filters.command('command'))` -- Parameters: - - command (str || list): - - The command to be handled for a function - - - group_only (bool) **optional**: - - If True, the command will only executed in Groups only, By Default False. - - - pm_only (bool) **optional**: - - If True, the command will only executed in Private Messages only, By Default False. - - - self_only (bool) **optional**: - - If True, the command will only excute if used by Self only, By Default False. - - - self_admin (bool) **optional**: - - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False - - - filter (`~pyrogram.filters`) **optional**: - - Pyrogram Filters, hope you know about this, for Advaced usage. By Default `~pyrogram.filters.edited` and this can't be changed. Use `and` for seaperating filters. - -#### Example -.. code-block:: python - import pyrogram - from tgEasy import command - - @command("start", group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text) - async def start(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") - """ - if filter: - if self_only: - filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & filter &filters.me if self_only else pyrogram.filters.command(command, prefixes=Config.HANDLER) & ~pyrogram.filters.edited & filter & pyrogram.filters.me - else: - filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & filter &filters.me if self_only else pyrogram.filters.command(command, prefixes=Config.HANDLER) & ~pyrogram.filters.edited & filter - else: - if self_only: - filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & pyrogram.filters.me +from tgEasy.scaffold import Scaffold + + +class Command(Scaffold): + def command(self, command: typing.Union[str, list], pm_only: typing.Union[bool, bool] = False, group_only: typing.Union[bool, bool] = False, self_admin: typing.Union[bool, bool] = False, self_only: typing.Union[bool] = False, filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, *args, **kwargs): + """ + ### `tgEasy.tgClient.command` + - A decorater to Register Commands in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_message(pyrogram.filters.command('command'))` + - Parameters: + - command (str || list): + - The command to be handled for a function + + - group_only (bool) **optional**: + - If True, the command will only executed in Groups only, By Default False. + + - pm_only (bool) **optional**: + - If True, the command will only executed in Private Messages only, By Default False. + + - self_only (bool) **optional**: + - If True, the command will only excute if used by Self only, By Default False. + + - self_admin (bool) **optional**: + - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False + + - filter (`~pyrogram.filters`) **optional**: + - Pyrogram Filters, hope you know about this, for Advaced usage. By Default `~pyrogram.filters.edited` and this can't be changed. Use `and` for seaperating filters. + + #### Example + .. code-block:: python + import pyrogram + from tgEasy import tgClient + + app = tgClient(pyrogram.Client()) + + @app.command("start", group_only=False, pm_only=False, self_admin=False, self_only=False, pyrogram.filters.chat("777000") and pyrogram.filters.text) + async def start(client, message): + await message.reply_text(f"Hello {message.from_user.mention}") + """ + if filter: + if self_only: + filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & filter & filters.me if self_only else pyrogram.filters.command( + command, prefixes=Config.HANDLER) & ~pyrogram.filters.edited & filter & pyrogram.filters.me + else: + filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & filter & filters.me if self_only else pyrogram.filters.command( + command, prefixes=Config.HANDLER) & ~pyrogram.filters.edited & filter else: - filter = pyrogram.filters.command(command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited - - def wrapper(func): - async def decorator(client, message: pyrogram.types.Message): - if self_admin and message.chat.type != "supergroup": - return await message.reply_text("This command can be used in supergroups only.") - if self_admin: - me = await client.get_me() - mee = await client.get_chat_member(message.chat.id, me.id) - if not mee.status == "admin": - return await message.reply_text("I must be admin to execute this Command") - pass - if group_only and message.chat.type != "supergroup": - return await message.reply_text("This command can be used in supergroups only.") - if pm_only and message.chat.type != "private": - return await message.reply_text("This command can be used in PMs only.") - try: - await func(client, message) - except pyrogram.errors.forbidden_403.ChatWriteForbidden: - await client.leave_chat(message.chat.id) - except BaseException as exception: - return await handle_error(exception, message) - tgEasy.tgClient.__client__.add_handler(pyrogram.handlers.MessageHandler(callback=decorator, filters=filter)) - return decorator - return wrapper - - -def callback(data: typing.Union[str, list], self_admin: typing.Union[bool, bool] = False, filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, *args, **kwargs): - """ -### `tgEasy.callback` - -- A decorater to Register Callback Quiries in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_callback_query(pyrogram.filters.regex('^data.*'))` -- Parameters: - - data (str || list): - - The callback query to be handled for a function - - - self_admin (bool) **optional**: - - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False - - - filter (`~pyrogram.filters`) **optional**: - - Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters. - -#### Example -.. code-block:: python - import pyrogram - from tgEasy import command, callback - - @command("start") - async def start(client, message): - await message.reply_text( - f"Hello {message.from_user.mention}", - reply_markup=pyrogram.types.InlineKeyboardMarkup([[ - pyrogram.types.InlineKeyboardButton( - "Click Here", - "data" + if self_only: + filter = pyrogram.filters.command( + command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited & pyrogram.filters.me + else: + filter = pyrogram.filters.command( + command, prefixes=Config.HANDLERS) & ~pyrogram.filters.edited + + def wrapper(func): + async def decorator(client, message: pyrogram.types.Message): + if self_admin and message.chat.type != "supergroup": + return await message.reply_text("This command can be used in supergroups only.") + if self_admin: + me = await client.get_me() + mee = await client.get_chat_member(message.chat.id, me.id) + if not mee.status == "admin": + return await message.reply_text("I must be admin to execute this Command") + pass + if group_only and message.chat.type != "supergroup": + return await message.reply_text("This command can be used in supergroups only.") + if pm_only and message.chat.type != "private": + return await message.reply_text("This command can be used in PMs only.") + try: + await func(client, message) + except pyrogram.errors.forbidden_403.ChatWriteForbidden: + await client.leave_chat(message.chat.id) + except BaseException as exception: + return await handle_error(exception, message) + self.__client__.add_handler(pyrogram.handlers.MessageHandler( + callback=decorator, filters=filter)) + return decorator + return wrapper + + +class Callback(Scaffold): + def callback(self, data: typing.Union[str, list], self_admin: typing.Union[bool, bool] = False, filter: typing.Union[pyrogram.filters.Filter, pyrogram.filters.Filter] = None, *args, **kwargs): + """ + ### `tgEasy.tgClient.callback` + + - A decorater to Register Callback Quiries in simple way and manage errors in that Function itself, alternative for `@pyrogram.Client.on_callback_query(pyrogram.filters.regex('^data.*'))` + - Parameters: + - data (str || list): + - The callback query to be handled for a function + + - self_admin (bool) **optional**: + - If True, the command will only executeed if the Bot is Admin in the Chat, By Default False + + - filter (`~pyrogram.filters`) **optional**: + - Pyrogram Filters, hope you know about this, for Advaced usage. Use `and` for seaperating filters. + + #### Example + .. code-block:: python + import pyrogram + from tgEasy import tgClient + + app = tgClient(pyrogram.Client()) + + @app.command("start") + async def start(client, message): + await message.reply_text( + f"Hello {message.from_user.mention}", + reply_markup=pyrogram.types.InlineKeyboardMarkup([[ + pyrogram.types.InlineKeyboardButton( + "Click Here", + "data" + ) + ]]) ) - ]]) - - @callback("data") - async def data(client, CallbackQuery): - await CallbackQuery.answer("Hello :)") - """ - if filter: - filter = pyrogram.filters.regex(f"^{data}.*") & args["filter"] - else: - filter = pyrogram.filters.regex(f"^{data}.*") - def wrapper(func): - async def decorator(client, CallbackQuery: pyrogram.types.CallbackQuery): - if self_admin: - me = await client.get_me() - mee = await client.get_chat_member(message.chat.id, me.id) - if not mee.status == "admin": - return await message.reply_text("I must be admin to execute this Command") - pass - try: - await func(client, CallbackQuery) - except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired: - pass - except BaseException as e: - return await handle_error(e, CallbackQuery) - tgEasy.tgClient.__client__.add_handler( - pyrogram.handlers.CallbackQueryHandler(decorator, filter)) - return decorator - return wrapper - -def adminsOnly(permission: typing.Union[str, list], TRUST_ANON_ADMINS: typing.Union[bool, bool] = False): - """ -### `tgEasy.adminsOnly` -- A decorater for running the function only if the admin have the specified Rights. -- We are still Working on this to make it to check Rights for Anonoymous Admins, Stay Tuned. -- Parameters: - - permission (str): - - Permission which the User must have to use the Functions - - - TRUST_ANON_ADMIN (bool) **optional**: - - If User is Anonymous Admin also, It Runs the Function, By Default False - -### Example -.. code-block:: python - from tgEasy import command, adminsOnly - - @command("start") - @adminsOnly("can_change_info") - async def start(client, message): - await message.reply_text(f"Hello Admin {message.from_user.mention}") - """ - def wrapper(func): - async def decorator(client, message): - if not message.chat.type == "supergroup": - return await message.reply_text("This command can be used in supergroups only.") - if message.sender_chat: - if not TRUST_ANON_ADMINS: - return await message.reply_text( - "The Right Check for Anonymous Admins is in Development. So you cannot perform this Action for Now, If you don't want this and want to Allow Anonymous Admins for performing Actions in this time Please Contact Bot Owner." - ) + + @app.callback("data") + async def data(client, CallbackQuery): + await CallbackQuery.answer("Hello :)", show_alert=True) + """ + if filter: + filter = pyrogram.filters.regex(f"^{data}.*") & args["filter"] + else: + filter = pyrogram.filters.regex(f"^{data}.*") + + def wrapper(func): + async def decorator(client, CallbackQuery: pyrogram.types.CallbackQuery): + if self_admin: + me = await client.get_me() + mee = await client.get_chat_member(message.chat.id, me.id) + if not mee.status == "admin": + return await message.reply_text("I must be admin to execute this Command") + pass try: - return await func(client, message) + await func(client, CallbackQuery) + except pyrogram.errors.exceptions.forbidden_403.ChatAdminRequired: + pass + except BaseException as e: + return await handle_error(e, CallbackQuery) + self.__client__.add_handler( + pyrogram.handlers.CallbackQueryHandler(decorator, filter)) + return decorator + return wrapper + + +class AdminsOnly(Scaffold): + def adminsOnly(self, permission: typing.Union[str, list], TRUST_ANON_ADMINS: typing.Union[bool, bool] = False): + """ + ### `tgEasy.tgClient.adminsOnly` + - A decorater for running the function only if the admin have the specified Rights. + - We are still Working on this to make it to check Rights for Anonoymous Admins, Stay Tuned. + - Parameters: + - permission (str): + - Permission which the User must have to use the Functions + + - TRUST_ANON_ADMIN (bool) **optional**: + - If User is Anonymous Admin also, It Runs the Function, By Default False + + ### Example + .. code-block:: python + from tgEasy import tgClient + import pyrogram + + app = tgClient(pyrogram.Client()) + + @app.command("start") + @app.adminsOnly("can_change_info") + async def start(client, message): + await message.reply_text(f"Hello Admin {message.from_user.mention}") + """ + def wrapper(func): + async def decorator(client, message): + if not message.chat.type == "supergroup": + return await message.reply_text("This command can be used in supergroups only.") + if message.sender_chat: + if not TRUST_ANON_ADMINS: + return await message.reply_text( + "The Right Check for Anonymous Admins is in Development. So you cannot perform this Action for Now, If you don't want this and want to Allow Anonymous Admins for performing Actions in this time Please Contact Bot Owner." + ) + if not await is_admin(message.chat.id, message.from_user.id, client=client): + return await message.reply_text("Only admins can execute this Command!") + if not await check_rights(message.chat.id, message.from_user.id, permission, client=client): + return await message.reply_text(f"You are Missing the following Rights to use this Command:\n{permission}") + try: + await func(client, message) except pyrogram.errors.exception.forbidden_403.ChatWriteForbidden: - return await client.leave_chat(message.chat.id) + await client.leave_chat(message.chat.id) except BaseException as exception: - return await handle_error(exception, message) - if not await is_admin(message.chat.id, message.from_user.id): - return await message.reply_text("Only admins can execute this Command!") - if not await check_rights(message.chat.id, message.from_user.id, permission): - return await message.reply_text(f"You are Missing the following Rights to use this Command:\n{permission}") - try: - await func(client, message) - except pyrogram.errors.exception.forbidden_403.ChatWriteForbidden: - await client.leave_chat(message.chat.id) - except BaseException as exception: - await handle_error(exception, message) - return decorator - return wrapper \ No newline at end of file + await handle_error(exception, message) + return decorator + return wrapper diff --git a/tgEasy/helpers.py b/tgEasy/helpers.py index ff2cd6a..41c11de 100644 --- a/tgEasy/helpers.py +++ b/tgEasy/helpers.py @@ -18,9 +18,14 @@ import os import typing + import pyrogram + import tgEasy +from .config import Config +from .scaffold import Scaffold + async def get_user(m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery]): """ @@ -34,13 +39,13 @@ async def get_user(m: typing.Union[pyrogram.types.Message, pyrogram.types.Callba #### Example .. code-block:: python - from tgEasy import get_user, command, adminsOnly + from tgEasy import get_user, command, adminsOnly - @command("ban", group_only=True, self_admin=True) - @adminsOnly("can_restrict_members") - async def ban(client, message): - user = await get_user(message) - await message.chat.kick_member(user.id) + @command("ban", group_only=True, self_admin=True) + @adminsOnly("can_restrict_members") + async def ban(client, message): + user = await get_user(message) + await message.chat.kick_member(user.id) """ if isinstance(m, pyrogram.types.Message): message = m @@ -84,6 +89,7 @@ async def ban(client, message): pass return False + async def get_user_adv(m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery]): """ ### `tgEasy.get_user_adv` @@ -97,12 +103,12 @@ async def get_user_adv(m: typing.Union[pyrogram.types.Message, pyrogram.types.Ca #### Example .. code-block:: python - from tgEasy import command, get_user_adv + from tgEasy import command, get_user_adv - @command("id") - async def id(client, message): - user = await get_user_adv(message) - await message.reply_text(f"Your ID is `{user.id}`") + @command("id") + async def id(client, message): + user = await get_user_adv(message) + await message.reply_text(f"Your ID is `{user.id}`") """ if isinstance(m, pyrogram.types.Message): message = m @@ -137,6 +143,7 @@ async def id(client, message): return await message._client.get_users(message.from_user.id) + async def send_typing(m: typing.Union[pyrogram.types.Message, pyrogram.types.CallbackQuery]): """ ### `tgEasy.send_typing` @@ -148,12 +155,15 @@ async def send_typing(m: typing.Union[pyrogram.types.Message, pyrogram.types.Cal #### Example .. code-block:: python - from tgEasy import command, send_typing + from tgEasy import tgClinet, send_typing + import pyrogram - @command("start") - async def start(client, message): - await send_typing(message) - await message.reply_text("Hello") + app = tgClient(pyrogram.Client()) + + @app.command("start") + async def start(client, message): + await send_typing(message) + await message.reply_text("Hello") """ if isinstance(m, pyrogram.types.Message): message = m @@ -179,17 +189,21 @@ async def handle_error(error, m: typing.Union[pyrogram.types.Message, pyrogram.t #### Exapmle .. code-block:: python - from tgEasy import command, handle_error + from tgEasy import tgClient, handle_error + import pyrogram + + app = tgClient(pyrogram.Client()) - @command("start") - async def start(client, message): + @app.command("start") + async def start(client, message): try: await message.reply_text("Hi :D') # I intentionally made an bug for Example :/ except Exceptation as e: return await handle_error(e, message) """ import traceback - import logging + + from . import logging with open("crash.log", "w+", encoding="utf-8") as log: log.write(traceback.format_exc()) log.close() @@ -210,39 +224,45 @@ async def start(client, message): os.remove('crash.log') -async def check_rights(chat_id: typing.Union[int, int], user_id: typing.Union[int, int], rights: typing.Union[str, str]) -> bool: +async def check_rights(chat_id: typing.Union[int, int], user_id: typing.Union[int, int], rights: typing.Union[str, str], client) -> bool: """ -### `tgEasy.check_rights` -- Checks the Rights of an User -- This is an Helper Function for `adminsOnly` + ### `tgEasy.check_rights` + - Checks the Rights of an User + - This is an Helper Function for `adminsOnly` -- Parameters: - - chat_id (int): - - The Chat ID of Which Chat have to check the Rights. + - Parameters: + - chat_id (int): + - The Chat ID of Which Chat have to check the Rights. - - user_id (int): - - The User ID of Whose Rights have to Check. + - user_id (int): + - The User ID of Whose Rights have to Check. - - rights (str): - - The Rights have to Check. + - rights (str): + - The Rights have to Check. -- Returns: - - `True` if the User have the Right. - - `False` if the User don't have the Right. + - client (`pyrogram.Client`): + - From which Client to Check the Rights. + + - Returns: + - `True` if the User have the Right. + - `False` if the User don't have the Right. + + #### Example + .. code-block:: python + from tgEasy import tgClient, check_rights, get_user + import pyrogram -### Example -.. code-block:: python - from tgEasy import command, check_rights, get_user + app = tgClient(pyrogram.Client()) - @command("ban", group_only=True, self_admin=True) - async def ban(client, message): + @app.command("ban", group_only=True, self_admin=True) + async def ban(client, message): if not await check_rights(message.chat.id, message.from_user.id, "can_restrict_members"): return await message.reply_text("You don't have necessary rights to use this Command.") user = await get_user(message) await message.chat.kick_member(user.id) """ try: - user = await tgEasy.tgClient.__client__.get_chat_member(chat_id, user_id) + user = await client.get_chat_member(chat_id, user_id) except: return False if user.status == "user": @@ -270,37 +290,44 @@ async def ban(client, message): return False return False -async def is_admin(chat_id: typing.Union[int, str], user_id: typing.Union[int, str]) -> bool: + +async def is_admin(chat_id: typing.Union[int, str], user_id: typing.Union[int, str], client) -> bool: """ -### `tgEasy.is_admin` -- A Functions to Check if the User is Admin or not + ### `tgEasy.is_admin` + - A Functions to Check if the User is Admin or not -- Parameters: - - chat_id (int): - - The Chat ID of Which Chat have to check the Admin Status. + - Parameters: + - chat_id (int): + - The Chat ID of Which Chat have to check the Admin Status. - - user_id (int): - - The User ID of Whose Admin Status have to Check. + - user_id (int): + - The User ID of Whose Admin Status have to Check. -- Returns: - - `True` if the User is Admin. - - `False` if the User is't Admin. - #### Example -.. code-block:: python -from tgEasy import command, is_admin, adminsOnly - -@command("ban", group_only=True, self_admin=True) -@adminsOnly("can_restrict_members") -async def ban(client, message): - if await is_admin(message.chat.id, (await get_user(mesasge)).id): - return await message.reply_text("You can't Ban Admins.") - await message.chat.kick_member((await get_user(message)).id) - await message.reply_text("User has been Banned.") + - client (`pyrogram.Client`): + - From which Client to Check the Admin Status. + + - Returns: + - `True` if the User is Admin. + - `False` if the User is't Admin. + #### Example + .. code-block:: python + from tgEasy import tgClient, is_admin, adminsOnly + import pyrogram + + app = tgClient(pyrogram.Client()) + + @app.command("ban", group_only=True, self_admin=True) + @app.adminsOnly("can_restrict_members") + async def ban(client, message): + if await is_admin(message.chat.id, (await get_user(mesasge)).id): + return await message.reply_text("You can't Ban Admins.") + await message.chat.kick_member((await get_user(message)).id) + await message.reply_text("User has been Banned.") """ try: - user = await tgEasy.tgClient.__client__.get_chat_member(chat_id, user_id) + user = await client.get_chat_member(chat_id, user_id) except: return False if user.status == "administrator" or user.status == "creator": return True - return False \ No newline at end of file + return False diff --git a/tgEasy/scaffold.py b/tgEasy/scaffold.py new file mode 100644 index 0000000..8460823 --- /dev/null +++ b/tgEasy/scaffold.py @@ -0,0 +1,33 @@ +import asyncio +import os +import platform +import re +import sys +import pyrogram +import tgEasy + +class Scaffold: + def __init__(self): + try: + asyncio.get_event_loop() + except RuntimeError: + # This happens when creating Client instances inside different threads that don't have an event loop. + # Set the main event loop in this thread. + asyncio.set_event_loop(tgEasy.main_event_loop) + + self.__client__ = None + + def command(*args, **kwargs): + pass + + def callback(*args, **kwargs): + pass + + def adminsOnly(*args, **kwargs): + pass + + async def check_rights(*args, **kwargs): + pass + + async def is_admin(*args, **kwargs): + pass \ No newline at end of file