├── .gitignore ├── LICENCE ├── README.md ├── examples └── MRE.py ├── pyrogram_patch ├── __init__.py ├── dispatcher.py ├── fsm │ ├── __init__.py │ ├── base_storage.py │ ├── filter.py │ ├── states.py │ └── storages │ │ ├── __init__.py │ │ ├── memory_storage.py │ │ └── redis_storage.py ├── middlewares │ ├── __init__.py │ └── middleware_types │ │ ├── __init__.py │ │ └── middlewares.py ├── patch.py ├── patch_data_pool.py ├── patch_helper.py └── router │ ├── __init__.py │ ├── patched_decorators │ ├── __init__.py │ ├── on_callback_query.py │ ├── on_chat_join_request.py │ ├── on_chat_member_updated.py │ ├── on_chosen_inline_result.py │ ├── on_deleted_messages.py │ ├── on_disconnect.py │ ├── on_edited_message.py │ ├── on_inline_query.py │ ├── on_message.py │ ├── on_poll.py │ ├── on_raw_update.py │ └── on_user_status.py │ └── router.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | .idea 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | 142 | main.py 143 | my_account.session 144 | my_account.session-journal -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 kotttee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyrogram_patch 2 | 3 | pyrogram_patch is a Python library this is a library that adds middlewares and fsm to pyrogram. 4 | 5 | ## Installation 6 | 7 | pip install git+https://github.com/kotttee/pyrogram_patch.git 8 | 9 | # Middlewares 10 | 11 | ## Usage 12 | 13 | ```python 14 | from pyrogram import Client 15 | from pyrogram_patch import patch 16 | 17 | # create client 18 | app = Client("my_account", api_id='API_ID', api_hash='API_HASH') 19 | 20 | # patch client 21 | patch_manager = patch(app) 22 | 23 | # include middleware 24 | patch_manager.include_middleware(MyMiddleware(*args, **kwargs)) 25 | 26 | ``` 27 | 28 | ## Create middleware 29 | 30 | ```python 31 | from pyrogram_patch.middlewares.middleware_types import OnUpdateMiddleware 32 | from pyrogram_patch.middlewares import PatchHelper 33 | 34 | 35 | class MyMiddleware(OnUpdateMiddleware): 36 | 37 | # it can be any value you want 38 | 39 | def __init__(self, *args, **kwargs) -> None: 40 | self.value = 'my_value' 41 | 42 | # you cannot change the call arguments 43 | async def __call__(self, update, client, patch_helper: PatchHelper): 44 | # do whatever you want 45 | patch_helper.data['my_value_name'] = self.value 46 | 47 | # get_data() - use this method to get the data you saved earlier 48 | # skip_handler() - use this method to skip the handler 49 | # patch_helper.state.state - this way you can get the current state 50 | ``` 51 | 52 | 53 | ## Handle midleware data 54 | 55 | ```python 56 | @app.on_message(filters.me) 57 | async def my_commands(client, message, my_value_name): 58 | print(my_value_name) 59 | ``` 60 | ## Middleware types and updates 61 | ```text 62 | middleware - middleware which is called if the update is used 63 | outer middleware - middleware that handles everything even if it wasn't caught by the handler 64 | ``` 65 | events and middlewares 66 | ```text 67 | on_message - OnMessageMiddleware 68 | on_inline_query - OnInlineQueryMiddleware 69 | on_user_status - OnUserStatusMiddleware 70 | on_disconnect - OnDisconnectMiddleware 71 | on_edited_message - OnEditedMessageMiddleware 72 | on_deleted_messages - OnDeletedMessagesMiddleware 73 | on_chosen_inline_result - OnChosenInlineResultMiddleware 74 | on_chat_member_updated - OnChatMemberUpdatedMiddleware 75 | on_raw_update - OnRawUpdateMiddleware 76 | on_chat_join_request - OnChatJoinRequestMiddleware 77 | on_callback_query - OnCallbackQueryMiddleware 78 | on_poll - OnPoolMiddleware 79 | 80 | OnUpdateMiddleware - middleware that reacts to everything 81 | 82 | MixedMiddleware - middleware that reacts to certain types of handlers 83 | 84 | pass the types of handlers from pyrogram.handlers during initialization that the malware will process 85 | 86 | patch_manager.include_middleware(ExampleMiddleware((MessageHandler, EditedMessageHandler), False)) 87 | 88 | 89 | class ExampleMiddleware(MixedMiddleware): 90 | def __init__(self, handlers: tuple, ignore: bool) -> None: 91 | self.ignore = ignore # it can be any value you want 92 | super().__init__(handlers) 93 | ``` 94 | everything you can import from 95 | ```text 96 | from pyrogram_patch.middlewares.middleware_types 97 | ``` 98 | 99 | # FSM 100 | allowed update types you can manage with 101 | app.dispatcher.manage_allowed_update_types(pyrogram.types.Update) 102 | ## Usage 103 | 104 | ```python 105 | from pyrogram import Client 106 | from pyrogram_patch import patch 107 | from pyrogram_patch.fsm.storages import MemoryStorage 108 | 109 | # create client 110 | app = Client("my_account", api_id='API_ID', api_hash='API_HASH') 111 | 112 | # patch client 113 | patch_manager = patch(app) 114 | 115 | # include middleware 116 | patch_manager.set_storage(MemoryStorage()) 117 | 118 | ``` 119 | 120 | ## Creating state groups 121 | 122 | ```python 123 | from pyrogram_patch.fsm import StatesGroup, StateItem 124 | 125 | class Parameters(StatesGroup): 126 | weight = StateItem() 127 | height = StateItem() 128 | ``` 129 | ## Processing and filtering data 130 | 131 | ```python 132 | from pyrogram_patch.fsm import State 133 | from pyrogram_patch.fsm.filter import StateFilter 134 | 135 | 136 | @app.on_message(filters.private & StateFilter()) # the same as StateFilter("*"), catches all states 137 | async def process_1(client: Client, message, state: State): 138 | if message.text == 'my_data': 139 | await client.send_message(message.chat.id, 'enter your weight') 140 | await state.set_state(Parameters.weight) 141 | 142 | 143 | @app.on_message(filters.private & StateFilter(Parameters.weight)) 144 | async def process_2(client: Client, message, state: State): 145 | await state.set_data({'weight': message.text}) 146 | await client.send_message(message.chat.id, 'enter your height') 147 | await state.set_state(Parameters.height) 148 | 149 | 150 | @app.on_message(filters.private & StateFilter(Parameters.height)) 151 | async def process_3(client: Client, message, state: State): 152 | state_data = await state.get_data() 153 | weight = state_data['weight'] 154 | await client.send_message(message.chat.id, 'your height - {} your weight - {}'.format(message.text, weight)) 155 | await state.finish() 156 | ``` 157 | ## Writing your own storages 158 | ```python 159 | from pyrogram_patch.fsm import State, BaseStorage 160 | 161 | 162 | class YourStorage(BaseStorage): 163 | 164 | def __init__(self) -> None: 165 | ... 166 | 167 | async def checkup(self, key) -> State: 168 | ... 169 | 170 | 171 | async def set_state(self, state: str, key: str) -> None: 172 | ... 173 | 174 | async def set_data(self, data: dict, key: str) -> None: 175 | ... 176 | 177 | async def get_data(self, key: str) -> dict: 178 | ... 179 | 180 | async def finish_state(self, key: str) -> None: 181 | ... 182 | 183 | # don't forget to make a pull request to the patch's GitHub 😉 184 | ``` 185 | 186 | ## Using filters with outer_middlewares 187 | only works with a few types of updates 188 | ```python 189 | async def my_filter(_, __, update) -> bool: 190 | if hasattr(update, "text"): 191 | patch_helper = PatchHelper.get_from_pool(update) 192 | patch_helper.data["integer"] = 1 193 | return True # False 194 | return False 195 | my_filter = filters.create(my_filter) 196 | ``` 197 | 198 | ## Routers 199 | 200 | ```python 201 | from pyrogram_patch.router import Router 202 | 203 | 204 | my_router = Router() 205 | 206 | @my_router.on_message(filters.me) 207 | async def my_commands(client, message, my_value_name, some_data): 208 | print(my_value_name) 209 | ``` 210 | 211 | main.py 212 | 213 | ```python 214 | patch_manager.include_router(my_router) 215 | ``` 216 | 217 | # Contributing 218 | Pull requests are welcome. For major changes, please open a question first to discuss what you would like to change. 219 | 220 | Be sure to update tests as needed. 221 | 222 | 223 | 224 | github: https://github.com/kotttee/pyrogram_patch 225 | 226 | telegram: https://t.me/kotttee 227 | 228 | ## License 229 | [MIT](https://choosealicense.com/licenses/mit/) 230 | -------------------------------------------------------------------------------- /examples/MRE.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.handlers import EditedMessageHandler, MessageHandler 3 | from pyrogram.types import Message 4 | 5 | from pyrogram_patch import patch 6 | from pyrogram_patch.fsm import State, StateItem, StatesGroup 7 | from pyrogram_patch.fsm.filter import StateFilter 8 | from pyrogram_patch.fsm.storages import MemoryStorage 9 | from pyrogram_patch.middlewares import PatchHelper 10 | from pyrogram_patch.middlewares.middleware_types import (MixedMiddleware, 11 | OnMessageMiddleware) 12 | from pyrogram_patch.router import Router 13 | 14 | SESSION_NAME = "bot" 15 | API_ID = 8 16 | API_HASH = "7245de8e747a0d6fbe11f7cc14fcc0bb" 17 | BOT_TOKEN = "" 18 | 19 | """FSM""" 20 | 21 | 22 | class Parameters(StatesGroup): 23 | weight = StateItem() 24 | height = StateItem() 25 | 26 | 27 | """MIDDLEWARES""" 28 | 29 | 30 | class SkipDigitMiddleware(OnMessageMiddleware): 31 | def __init__(self) -> None: 32 | pass 33 | 34 | async def __call__(self, message: Message, client: Client, patch_helper: PatchHelper): 35 | is_digit = patch_helper.data["is_digit"] 36 | if not is_digit: 37 | if patch_helper.state.state == Parameters.height: 38 | return await patch_helper.skip_handler() 39 | 40 | 41 | class CheckDigitMiddleware(MixedMiddleware): 42 | def __init__(self, handlers: tuple, some_var: bool) -> None: 43 | self.some_var = some_var # it can be any value you want 44 | super().__init__(handlers) 45 | 46 | async def __call__(self, message: Message, client: Client, patch_helper: PatchHelper): 47 | if hasattr(message, "text"): 48 | patch_helper.data["is_digit"] = message.text.isdigit() 49 | 50 | 51 | """APP""" 52 | 53 | app = Client(...) 54 | 55 | router = Router() 56 | router2 = Router() 57 | 58 | patch_manager = patch(app) 59 | patch_manager.set_storage(MemoryStorage()) 60 | patch_manager.include_outer_middleware( 61 | CheckDigitMiddleware((MessageHandler, ), False) 62 | ) 63 | patch_manager.include_middleware(SkipDigitMiddleware()) 64 | patch_manager.include_router(router) 65 | patch_manager.include_router(router2) 66 | 67 | 68 | async def my_filter_function(_, __, update) -> bool: 69 | if hasattr(update, "text"): 70 | patch_helper = PatchHelper.get_from_pool(update) 71 | some_data = patch_helper.data["is_digit"] 72 | patch_helper.data["some_data_is_digit"] = some_data 73 | return True # False 74 | return False 75 | 76 | 77 | my_filter = filters.create(my_filter_function) 78 | 79 | 80 | @router.on_message(filters.private & StateFilter() & my_filter) 81 | async def process_1(client: Client, message, state: State, some_data_is_digit: bool): 82 | print(some_data_is_digit) 83 | if message.text == "register": 84 | await client.send_message(message.chat.id, "enter your weight") 85 | await state.set_state(Parameters.weight) 86 | 87 | 88 | @router2.on_message(filters.private & StateFilter(Parameters.weight)) 89 | async def process_2(client: Client, message, state: State): 90 | await state.set_data({"weight": message.text}) 91 | await client.send_message(message.chat.id, "enter your height") 92 | await state.set_state(Parameters.height) 93 | 94 | 95 | @app.on_message(filters.private & StateFilter(Parameters.height)) 96 | async def process_3(client: Client, message, state: State): 97 | state_data = await state.get_data() 98 | weight = state_data["weight"] 99 | await client.send_message( 100 | message.chat.id, f"your height - {message.text} your weight - {weight}" 101 | ) 102 | await state.finish() 103 | 104 | 105 | app.run() 106 | -------------------------------------------------------------------------------- /pyrogram_patch/__init__.py: -------------------------------------------------------------------------------- 1 | from .middlewares import middleware_types 2 | from .patch import PatchManager, patch 3 | 4 | __all__ = ["patch", "PatchManager"] 5 | -------------------------------------------------------------------------------- /pyrogram_patch/dispatcher.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from contextlib import suppress 3 | from typing import Union 4 | 5 | import pyrogram 6 | from pyrogram.dispatcher import Dispatcher, log 7 | from pyrogram.handlers import RawUpdateHandler 8 | from .patch_data_pool import PatchDataPool 9 | from pyrogram_patch.fsm import BaseStorage 10 | from pyrogram_patch.middlewares import PatchHelper 11 | 12 | 13 | class PatchedDispatcher(Dispatcher): 14 | def __init__(self, client: pyrogram.Client): 15 | super().__init__(client) 16 | self.patch_data_pool = PatchDataPool 17 | 18 | async def handler_worker(self, lock): 19 | while True: 20 | packet = await self.updates_queue.get() 21 | 22 | if packet is None: 23 | break 24 | 25 | try: 26 | update, users, chats = packet 27 | parser = self.update_parsers.get(type(update), None) 28 | 29 | parsed_updates, handler_type = ( 30 | await parser(update, users, chats) 31 | if parser is not None 32 | else (None, type(None)) 33 | ) 34 | 35 | if parsed_updates is None: 36 | continue 37 | 38 | patch_helper = PatchHelper() 39 | PatchDataPool.include_helper_to_pool(parsed_updates, patch_helper) 40 | 41 | if PatchDataPool.pyrogram_patch_fsm_storage: 42 | await patch_helper._include_state( 43 | parsed_updates, PatchDataPool.pyrogram_patch_fsm_storage, self.client 44 | ) 45 | 46 | # process outer middlewares 47 | for middleware in PatchDataPool.pyrogram_patch_outer_middlewares: 48 | if middleware == handler_type: 49 | await patch_helper._process_middleware( 50 | parsed_updates, middleware, self.client 51 | ) 52 | 53 | async with lock: 54 | for group in self.groups.values(): 55 | for handler in group: 56 | args = None 57 | 58 | if isinstance(handler, handler_type): 59 | try: 60 | # filtering event 61 | if await handler.check(self.client, parsed_updates): 62 | # process middlewares 63 | for middleware in PatchDataPool.pyrogram_patch_middlewares: 64 | if middleware == type(handler): 65 | await patch_helper._process_middleware( 66 | parsed_updates, 67 | middleware, 68 | self.client, 69 | ) 70 | args = (parsed_updates,) 71 | except Exception as e: 72 | log.exception(e) 73 | PatchDataPool.exclude_helper_from_pool(parsed_updates) 74 | continue 75 | 76 | elif isinstance(handler, RawUpdateHandler): 77 | try: 78 | # process middlewares 79 | for middleware in PatchDataPool.pyrogram_patch_middlewares: 80 | if middleware == type(handler): 81 | await patch_helper._process_middleware( 82 | parsed_updates, 83 | middleware, 84 | self.client, 85 | ) 86 | args = (update, users, chats) 87 | except pyrogram.StopPropagation: 88 | PatchDataPool.exclude_helper_from_pool(parsed_updates) 89 | continue 90 | if args is None: 91 | continue 92 | 93 | try: 94 | # formation kwargs 95 | kwargs = await patch_helper._get_data_for_handler( 96 | handler.callback.__code__.co_varnames 97 | ) 98 | if inspect.iscoroutinefunction(handler.callback): 99 | await handler.callback(self.client, *args, **kwargs) 100 | else: 101 | args = list(args) 102 | for v in kwargs.values(): 103 | args.append(v) 104 | args = tuple(args) 105 | await self.loop.run_in_executor( 106 | self.client.executor, 107 | handler.callback, 108 | self.client, 109 | *args 110 | ) 111 | except pyrogram.StopPropagation: 112 | raise 113 | except pyrogram.ContinuePropagation: 114 | continue 115 | except Exception as e: 116 | log.exception(e) 117 | finally: 118 | PatchDataPool.exclude_helper_from_pool(parsed_updates) 119 | break 120 | PatchDataPool.exclude_helper_from_pool(parsed_updates) 121 | except pyrogram.StopPropagation: 122 | pass 123 | except Exception as e: 124 | log.exception(e) 125 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_storage import BaseStorage 2 | from .states import State, StateItem, StatesGroup 3 | 4 | __all__ = ["State", "StatesGroup", "StateItem", "BaseStorage"] 5 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/base_storage.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from .states import State 4 | 5 | 6 | class BaseStorage(ABC): 7 | @abstractmethod 8 | async def checkup(self, key) -> State: 9 | ... 10 | 11 | @abstractmethod 12 | async def set_state(self, state: str, key: str) -> None: 13 | ... 14 | 15 | @abstractmethod 16 | async def set_data(self, data: dict, key: str) -> None: 17 | ... 18 | 19 | @abstractmethod 20 | async def get_data(self, key: str) -> dict: 21 | ... 22 | 23 | @abstractmethod 24 | async def finish_state(self, key: str) -> None: 25 | ... 26 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/filter.py: -------------------------------------------------------------------------------- 1 | from ..patch_helper import PatchHelper 2 | 3 | 4 | class StateFilter: 5 | def __init__(self, state: str = "*") -> None: 6 | self.state = state 7 | self.__name__ = state + "_state_filter" 8 | 9 | def __call__(_, __, query) -> bool: 10 | try: 11 | return _.state == PatchHelper.get_from_pool(query).state.name 12 | except Exception: 13 | raise RuntimeError( 14 | 'check storage' 15 | ) -------------------------------------------------------------------------------- /pyrogram_patch/fsm/states.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | class State: 5 | def __init__(self, name: str, storage: "BaseStorage", key: str) -> None: 6 | self.name = name 7 | self.__storage = storage 8 | self.__key = key 9 | 10 | async def set_state(self, state) -> None: 11 | await self.__storage.set_state(state, self.__key) 12 | self.name = state 13 | 14 | async def set_data(self, data: dict) -> None: 15 | await self.__storage.set_data(data, self.__key) 16 | 17 | async def get_data(self) -> dict: 18 | return await self.__storage.get_data(self.__key) 19 | 20 | async def finish(self) -> None: 21 | await self.__storage.finish_state(self.__key) 22 | 23 | async def create_state(self, key: str) -> "State": 24 | return await self.__storage.checkup(key) 25 | 26 | def __repr__(self) -> str: 27 | return f"State(name - {self.name} | key - {self.__key})" 28 | 29 | @property 30 | def state(self) -> str: 31 | return self.name 32 | 33 | 34 | class StateItem: 35 | def __get__(self, obj, cls): 36 | for name, obj in vars(cls).items(): 37 | if obj is self: 38 | return "StatesGroup_" + cls.__name__ + "_State_" + name 39 | 40 | 41 | @dataclass(init=False, frozen=True) 42 | class StatesGroup: 43 | pass 44 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/storages/__init__.py: -------------------------------------------------------------------------------- 1 | from .memory_storage import MemoryStorage 2 | from .redis_storage import RedisStorage 3 | 4 | __all__ = ["MemoryStorage", "RedisStorage"] 5 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/storages/memory_storage.py: -------------------------------------------------------------------------------- 1 | from pyrogram_patch.fsm.base_storage import BaseStorage 2 | from pyrogram_patch.fsm.states import State 3 | 4 | 5 | class MemoryStorage(BaseStorage): 6 | def __init__(self) -> None: 7 | self.__storage = {} 8 | self.__data_storage = {} 9 | 10 | async def checkup(self, key) -> "State": 11 | if key not in self.__storage.keys(): 12 | return State("*", self, key) 13 | return State(self.__storage[key], self, key) 14 | 15 | async def set_state(self, state: str, key: str) -> None: 16 | self.__storage[key] = state 17 | 18 | async def set_data(self, data: dict, key: str) -> None: 19 | if key in self.__data_storage: 20 | self.__data_storage[key].update(data) 21 | else: 22 | self.__data_storage[key] = data 23 | 24 | async def get_data(self, key: str) -> dict: 25 | if key in self.__data_storage: 26 | return self.__data_storage[key] 27 | return {} 28 | 29 | async def finish_state(self, key: str) -> None: 30 | if key in self.__storage.keys(): 31 | self.__storage.pop(key) 32 | if key in self.__data_storage.keys(): 33 | self.__data_storage.pop(key) 34 | -------------------------------------------------------------------------------- /pyrogram_patch/fsm/storages/redis_storage.py: -------------------------------------------------------------------------------- 1 | from json import dumps, loads 2 | from typing import Any, Dict, Optional 3 | from redis.asyncio.client import Redis 4 | from redis.asyncio.connection import ConnectionPool 5 | from pyrogram_patch.fsm.base_storage import BaseStorage 6 | from pyrogram_patch.fsm.states import State 7 | 8 | 9 | class RedisStorage(BaseStorage): 10 | def __init__(self, __storage: Redis) -> None: 11 | self.__storage = __storage 12 | 13 | @classmethod 14 | def from_url( 15 | cls, url: str, connection_kwargs: Optional[Dict[str, Any]] = None 16 | ) -> "RedisStorage": 17 | if connection_kwargs is None: 18 | connection_kwargs = {} 19 | pool = ConnectionPool.from_url(url, **connection_kwargs) 20 | redis = Redis(connection_pool=pool) 21 | return cls(__storage=redis) 22 | 23 | async def checkup(self, key) -> "State": 24 | if await self.__storage.exists(key): 25 | return State(await self.__storage.get(key), self, key) 26 | return State("*", self, key) 27 | 28 | async def set_state(self, state: str, key: str) -> None: 29 | if state is None: 30 | await self.__storage.delete(key) 31 | else: 32 | await self.__storage.set( 33 | key, 34 | state 35 | ) 36 | 37 | async def set_data(self, data: dict, key: str) -> None: 38 | if not data: 39 | await self.__storage.delete(key) 40 | return 41 | await self.__storage.set( 42 | key, 43 | dumps(data) 44 | ) 45 | 46 | async def get_data(self, key: str) -> dict: 47 | if key is None: 48 | return {} 49 | if isinstance(key, bytes): 50 | key = key.decode("utf-8") 51 | return loads(await self.__storage.get(key)) 52 | 53 | async def finish_state(self, key: str) -> None: 54 | await self.__storage.delete(key) 55 | -------------------------------------------------------------------------------- /pyrogram_patch/middlewares/__init__.py: -------------------------------------------------------------------------------- 1 | from pyrogram_patch.patch_helper import PatchHelper 2 | 3 | from . import middleware_types 4 | 5 | __all__ = ["middleware_types", "PatchHelper"] 6 | -------------------------------------------------------------------------------- /pyrogram_patch/middlewares/middleware_types/__init__.py: -------------------------------------------------------------------------------- 1 | from . import middlewares 2 | from .middlewares import * 3 | 4 | __all__ = [ 5 | "OnMessageMiddleware", 6 | "OnPoolMiddleware", 7 | "OnDeletedMessagesMiddleware", 8 | "OnDisconnectMiddleware", 9 | "OnRawUpdateMiddleware", 10 | "OnUserStatusMiddleware", 11 | "OnInlineQueryMiddleware", 12 | "OnCallbackQueryMiddleware", 13 | "OnChosenInlineResultMiddleware", 14 | "OnEditedMessageMiddleware", 15 | "OnChatJoinRequestMiddleware", 16 | "OnChatMemberUpdatedMiddleware", 17 | "OnUpdateMiddleware", 18 | "MixedMiddleware" 19 | ] 20 | -------------------------------------------------------------------------------- /pyrogram_patch/middlewares/middleware_types/middlewares.py: -------------------------------------------------------------------------------- 1 | from pyrogram.handlers.callback_query_handler import CallbackQueryHandler 2 | from pyrogram.handlers.chat_join_request_handler import ChatJoinRequestHandler 3 | from pyrogram.handlers.chat_member_updated_handler import \ 4 | ChatMemberUpdatedHandler 5 | from pyrogram.handlers.chosen_inline_result_handler import \ 6 | ChosenInlineResultHandler 7 | from pyrogram.handlers.deleted_messages_handler import DeletedMessagesHandler 8 | from pyrogram.handlers.disconnect_handler import DisconnectHandler 9 | from pyrogram.handlers.edited_message_handler import EditedMessageHandler 10 | from pyrogram.handlers.inline_query_handler import InlineQueryHandler 11 | from pyrogram.handlers.message_handler import MessageHandler 12 | from pyrogram.handlers.poll_handler import PollHandler 13 | from pyrogram.handlers.raw_update_handler import RawUpdateHandler 14 | from pyrogram.handlers.user_status_handler import UserStatusHandler 15 | 16 | 17 | class OnUpdateMiddleware: 18 | """middleware for all""" 19 | 20 | def __eq__(self, other) -> bool: 21 | return True 22 | 23 | 24 | class OnEditedMessageMiddleware: 25 | """middleware for on_edited_message""" 26 | 27 | def __eq__(self, other) -> bool: 28 | return other == EditedMessageHandler 29 | 30 | 31 | class OnUserStatusMiddleware: 32 | """middleware for on_user_status""" 33 | 34 | def __eq__(self, other) -> bool: 35 | return other == UserStatusHandler 36 | 37 | 38 | class OnRawUpdateMiddleware: 39 | """middleware for on_raw_update""" 40 | 41 | def __eq__(self, other) -> bool: 42 | return other == RawUpdateHandler 43 | 44 | 45 | class OnChosenInlineResultMiddleware: 46 | """middleware for on_chosen_inline_result""" 47 | 48 | def __eq__(self, other) -> bool: 49 | return other == ChosenInlineResultHandler 50 | 51 | 52 | class OnDeletedMessagesMiddleware: 53 | """middleware for on_deleted_messages""" 54 | 55 | def __eq__(self, other) -> bool: 56 | return other == DeletedMessagesHandler 57 | 58 | 59 | class OnChatMemberUpdatedMiddleware: 60 | """middleware for on_chat_member_updated""" 61 | 62 | def __eq__(self, other) -> bool: 63 | return other == ChatMemberUpdatedHandler 64 | 65 | 66 | class OnChatJoinRequestMiddleware: 67 | """middleware for on_chat_join_request""" 68 | 69 | def __eq__(self, other) -> bool: 70 | return other == ChatJoinRequestHandler 71 | 72 | 73 | class OnCallbackQueryMiddleware: 74 | """middleware for on_callback_query""" 75 | 76 | def __eq__(self, other) -> bool: 77 | return other == CallbackQueryHandler 78 | 79 | 80 | class OnInlineQueryMiddleware: 81 | """middleware for on_inline_query""" 82 | 83 | def __eq__(self, other) -> bool: 84 | return other == InlineQueryHandler 85 | 86 | 87 | class OnDisconnectMiddleware: 88 | """middleware for on_disconnect""" 89 | 90 | def __eq__(self, other) -> bool: 91 | return other == DisconnectHandler 92 | 93 | 94 | class OnMessageMiddleware: 95 | """middleware for on_message""" 96 | 97 | def __eq__(self, other) -> bool: 98 | return other == MessageHandler 99 | 100 | 101 | class OnPoolMiddleware: 102 | """middleware for on_pool""" 103 | 104 | def __eq__(self, other) -> bool: 105 | return other == PollHandler 106 | 107 | 108 | class MixedMiddleware: 109 | """pass the types of handlers from pyrogram.handlers during initialization that the malware will process 110 | 111 | patch_manager.include_middleware(CheckIgnoreMiddleware((MessageHandler, EditedMessageHandler), False)) 112 | 113 | 114 | class CheckIgnoreMiddleware(MixedMiddleware): 115 | def __init__(self, handlers: tuple, ignore: bool) -> None: 116 | self.ignore = ignore # it can be any value you want 117 | super().__init__(handlers) 118 | """ 119 | 120 | def __init__(self, handlers: tuple) -> None: 121 | self._handlers = handlers 122 | 123 | def __eq__(self, other) -> bool: 124 | return other in self._handlers 125 | -------------------------------------------------------------------------------- /pyrogram_patch/patch.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | 3 | from pyrogram_patch.fsm import BaseStorage 4 | from .patch_data_pool import PatchDataPool 5 | 6 | from .dispatcher import PatchedDispatcher 7 | from .router import Router 8 | 9 | 10 | class PatchManager: 11 | def __init__(self, client: Client): 12 | self.client = client 13 | self.dispatcher = client.dispatcher 14 | 15 | def include_middleware(self, middleware: "PatchMiddleware") -> None: 16 | PatchDataPool.pyrogram_patch_middlewares.append(middleware) 17 | 18 | def include_outer_middleware(self, middleware: "PatchMiddleware") -> None: 19 | PatchDataPool.pyrogram_patch_outer_middlewares.append(middleware) 20 | 21 | def set_storage(self, storage: BaseStorage) -> None: 22 | PatchDataPool.pyrogram_patch_fsm_storage = storage 23 | 24 | def include_router(self, router: Router) -> None: 25 | router.set_client(self.client) 26 | 27 | 28 | def patch(app: Client) -> PatchManager: 29 | """app - instance of your pyrogram client 30 | returns 31 | MiddlewarePatchManager instance with methods: 32 | include_middleware and include_outer_middleware 33 | """ 34 | app.__delattr__("dispatcher") 35 | app.dispatcher = PatchedDispatcher(app) 36 | return PatchManager(app) 37 | -------------------------------------------------------------------------------- /pyrogram_patch/patch_data_pool.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Union 3 | from pyrogram_patch.fsm import BaseStorage 4 | from contextlib import suppress 5 | 6 | 7 | @dataclass() 8 | class PatchDataPool: 9 | update_pool: dict 10 | pyrogram_patch_middlewares: list 11 | pyrogram_patch_outer_middlewares: list 12 | pyrogram_patch_fsm_storage: Union[BaseStorage, None] 13 | 14 | @staticmethod 15 | def include_helper_to_pool(update, patch_helper) -> None: 16 | PatchDataPool.update_pool[id(update)] = patch_helper 17 | 18 | @staticmethod 19 | def exclude_helper_from_pool(update) -> None: 20 | with suppress(KeyError): 21 | PatchDataPool.update_pool.pop(id(update)) 22 | 23 | @staticmethod 24 | def get_helper_from_pool(update) -> Union[object, None]: 25 | return PatchDataPool.update_pool[id(update)] 26 | 27 | 28 | PatchDataPool.update_pool = {} 29 | PatchDataPool.pyrogram_patch_middlewares = [] 30 | PatchDataPool.pyrogram_patch_outer_middlewares = [] 31 | PatchDataPool.pyrogram_patch_fsm_storage = None 32 | 33 | -------------------------------------------------------------------------------- /pyrogram_patch/patch_helper.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from typing import Any, Union 3 | from .patch_data_pool import PatchDataPool 4 | from pyrogram import Client, StopPropagation 5 | from pyrogram.handlers.handler import Handler 6 | 7 | 8 | # you can modify it 9 | async def create_key(parsed_update, client: Client) -> str: 10 | chat_id, user_id = "unknown", "unknown" 11 | if hasattr(parsed_update, "from_user"): 12 | if parsed_update.from_user is not None: 13 | user_id = str(parsed_update.from_user.id) 14 | if hasattr(parsed_update, "chat"): 15 | if parsed_update.chat is not None: 16 | chat_id = str(parsed_update.chat.id) 17 | elif hasattr(parsed_update, "message"): 18 | if hasattr(parsed_update.message, "chat"): 19 | if parsed_update.message.chat is not None: 20 | chat_id = str(parsed_update.message.chat.id) 21 | return str(client.me.id) + "-" + user_id + "-" + chat_id 22 | 23 | 24 | class PatchHelper: 25 | def __init__(self) -> None: 26 | self.__data = {} 27 | self.state = "*" 28 | 29 | async def skip_handler(self) -> None: 30 | """use this method to skip the handler""" 31 | raise StopPropagation("please ignore this error, it is raised by the PatchHelper.skip_handler method in one of your middlewares") 32 | 33 | async def _get_data_for_handler(self, arguments) -> dict: 34 | """PLEASE DON'T USE THIS""" 35 | kwargs = {} 36 | if "state" in arguments: 37 | kwargs["state"] = self.state 38 | if "patch_helper" in arguments: 39 | kwargs["patch_helper"] = self 40 | 41 | if len(self.__data) > 0: 42 | for k, v in self.__data.items(): 43 | if k in arguments: 44 | kwargs[k] = v 45 | return kwargs 46 | 47 | async def _process_middleware(self, parsed_update, middleware, client): 48 | """PLEASE DON'T USE THIS""" 49 | return await middleware(parsed_update, client, self) 50 | 51 | async def _include_state(self, parsed_update, storage, client): 52 | """PLEASE DON'T USE THIS""" 53 | self.state = await storage.checkup(await create_key(parsed_update, client)) 54 | 55 | @classmethod 56 | def get_from_pool(cls, update): 57 | helper = PatchDataPool.get_helper_from_pool(update) 58 | if helper is None: 59 | return cls() 60 | return helper 61 | 62 | @staticmethod 63 | def generate_state_key(client_id: int, user_id: Union[int, str] = "unknown", chat_id: Union[int, str] = "unknown") -> str: 64 | return str(client_id) + "-" + str(user_id) + "-" + str(chat_id) 65 | 66 | @property 67 | def data(self) -> dict: 68 | return self.__data 69 | 70 | def __repr__(self) -> str: 71 | return f"PatchHelper(state: {self.state} | data: {self.__data})" 72 | -------------------------------------------------------------------------------- /pyrogram_patch/router/__init__.py: -------------------------------------------------------------------------------- 1 | from .router import Router 2 | 3 | __all__ = ["Router"] 4 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/__init__.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from .on_callback_query import OnCallbackQuery 20 | from .on_chat_join_request import OnChatJoinRequest 21 | from .on_chat_member_updated import OnChatMemberUpdated 22 | from .on_chosen_inline_result import OnChosenInlineResult 23 | from .on_deleted_messages import OnDeletedMessages 24 | from .on_disconnect import OnDisconnect 25 | from .on_edited_message import OnEditedMessage 26 | from .on_inline_query import OnInlineQuery 27 | from .on_message import OnMessage 28 | from .on_poll import OnPoll 29 | from .on_raw_update import OnRawUpdate 30 | from .on_user_status import OnUserStatus 31 | 32 | 33 | class PatchedDecorators( 34 | OnMessage, 35 | OnEditedMessage, 36 | OnDeletedMessages, 37 | OnCallbackQuery, 38 | OnRawUpdate, 39 | OnDisconnect, 40 | OnUserStatus, 41 | OnInlineQuery, 42 | OnPoll, 43 | OnChosenInlineResult, 44 | OnChatMemberUpdated, 45 | OnChatJoinRequest, 46 | ): 47 | pass 48 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_callback_query.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnCallbackQuery: 27 | def on_callback_query(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling callback queries. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.CallbackQueryHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of callback queries to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.CallbackQueryHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.CallbackQueryHandler(func, filters), group)) 50 | 51 | else: 52 | raise RuntimeError( 53 | "you should only use this in routers, and only as a decorator" 54 | ) 55 | 56 | return func 57 | 58 | return decorator 59 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_chat_join_request.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnChatJoinRequest: 27 | def on_chat_join_request(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling chat join requests. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.ChatJoinRequestHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of updates to be passed in your function. 36 | 37 | group (``int``, *optional*): 38 | The group identifier, defaults to 0. 39 | """ 40 | 41 | def decorator(func: Callable) -> Callable: 42 | if isinstance(self, pyrogram_patch.router.Router): 43 | if self._app is not None: 44 | self._app.add_handler( 45 | pyrogram.handlers.ChatJoinRequestHandler(func, filters), group 46 | ) 47 | else: 48 | self._decorators_storage.append((pyrogram.handlers.ChatJoinRequestHandler(func, filters), group)) 49 | else: 50 | raise RuntimeError( 51 | "you should only use this in routers, and only as a decorator" 52 | ) 53 | 54 | return func 55 | 56 | return decorator 57 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_chat_member_updated.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnChatMemberUpdated: 27 | def on_chat_member_updated(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling event changes on chat members. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.ChatMemberUpdatedHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of updates to be passed in your function. 36 | 37 | group (``int``, *optional*): 38 | The group identifier, defaults to 0. 39 | """ 40 | 41 | def decorator(func: Callable) -> Callable: 42 | if isinstance(self, pyrogram_patch.router.Router): 43 | if self._app is not None: 44 | self._app.add_handler( 45 | pyrogram.handlers.ChatMemberUpdatedHandler(func, filters), group 46 | ) 47 | else: 48 | self._decorators_storage.append((pyrogram.handlers.ChatMemberUpdatedHandler(func, filters), group)) 49 | else: 50 | raise RuntimeError( 51 | "you should only use this in routers, and only as a decorator" 52 | ) 53 | 54 | return func 55 | 56 | return decorator 57 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_chosen_inline_result.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnChosenInlineResult: 27 | def on_chosen_inline_result(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling chosen inline results. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.ChosenInlineResultHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of chosen inline results to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.ChosenInlineResultHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.ChosenInlineResultHandler(func, filters), group)) 50 | else: 51 | raise RuntimeError( 52 | "you should only use this in routers, and only as a decorator" 53 | ) 54 | 55 | return func 56 | 57 | return decorator 58 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_deleted_messages.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnDeletedMessages: 27 | def on_deleted_messages(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling deleted messages. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.DeletedMessagesHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of messages to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.DeletedMessagesHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.DeletedMessagesHandler(func, filters), group)) 50 | else: 51 | raise RuntimeError( 52 | "you should only use this in routers, and only as a decorator" 53 | ) 54 | 55 | return func 56 | 57 | return decorator 58 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_disconnect.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnDisconnect: 27 | def on_disconnect(self=None) -> Callable: 28 | """Decorator for handling disconnections. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.DisconnectHandler`. 32 | """ 33 | 34 | def decorator(func: Callable) -> Callable: 35 | if isinstance(self, pyrogram_patch.router.Router): 36 | if self._app is not None: 37 | self._app.add_handler(pyrogram.handlers.DisconnectHandler(func)) 38 | else: 39 | self._decorators_storage.append(pyrogram.handlers.DisconnectHandler(func)) 40 | else: 41 | raise RuntimeError( 42 | "you should only use this in routers, and only as a decorator" 43 | ) 44 | 45 | return func 46 | 47 | return decorator 48 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_edited_message.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnEditedMessage: 27 | def on_edited_message(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling edited messages. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.EditedMessageHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of messages to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.EditedMessageHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.EditedMessageHandler(func, filters), group)) 50 | else: 51 | raise RuntimeError( 52 | "you should only use this in routers, and only as a decorator" 53 | ) 54 | 55 | return func 56 | 57 | return decorator 58 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_inline_query.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnInlineQuery: 27 | def on_inline_query(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling inline queries. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.InlineQueryHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of inline queries to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | 46 | self._app.add_handler( 47 | pyrogram.handlers.InlineQueryHandler(func, filters), group 48 | ) 49 | else: 50 | self._decorators_storage.append((pyrogram.handlers.InlineQueryHandler(func, filters), group)) 51 | else: 52 | raise RuntimeError( 53 | "you should only use this in routers, and only as a decorator" 54 | ) 55 | 56 | return func 57 | 58 | return decorator 59 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_message.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnMessage: 27 | def on_message(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling new messages. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.MessageHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of messages to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.MessageHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.MessageHandler(func, filters), group)) 50 | else: 51 | raise RuntimeError( 52 | "you should only use this in routers, and only as a decorator" 53 | ) 54 | 55 | return func 56 | 57 | return decorator 58 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_poll.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnPoll: 27 | def on_poll(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling poll updates. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.PollHandler`. 32 | 33 | Parameters: 34 | filters (:obj:`~pyrogram.filters`, *optional*): 35 | Pass one or more filters to allow only a subset of polls to be passed 36 | in your function. 37 | 38 | group (``int``, *optional*): 39 | The group identifier, defaults to 0. 40 | """ 41 | 42 | def decorator(func: Callable) -> Callable: 43 | if isinstance(self, pyrogram_patch.router.Router): 44 | if self._app is not None: 45 | self._app.add_handler( 46 | pyrogram.handlers.PollHandler(func, filters), group 47 | ) 48 | else: 49 | self._decorators_storage.append((pyrogram.handlers.PollHandler(func, filters), group)) 50 | else: 51 | raise RuntimeError( 52 | "you should only use this in routers, and only as a decorator" 53 | ) 54 | 55 | return func 56 | 57 | return decorator 58 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_raw_update.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnRawUpdate: 27 | def on_raw_update(self=None, group: int = 0) -> Callable: 28 | """Decorator for handling raw updates. 29 | 30 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 31 | :obj:`~pyrogram.handlers.RawUpdateHandler`. 32 | 33 | Parameters: 34 | group (``int``, *optional*): 35 | The group identifier, defaults to 0. 36 | """ 37 | 38 | def decorator(func: Callable) -> Callable: 39 | if isinstance(self, pyrogram_patch.router.Router): 40 | if self._app is not None: 41 | self._app.add_handler(pyrogram.handlers.RawUpdateHandler(func), group) 42 | else: 43 | self._decorators_storage.append((pyrogram.handlers.RawUpdateHandler(func), group)) 44 | else: 45 | raise RuntimeError( 46 | "you should only use this in routers, and only as a decorator" 47 | ) 48 | 49 | return func 50 | 51 | return decorator 52 | -------------------------------------------------------------------------------- /pyrogram_patch/router/patched_decorators/on_user_status.py: -------------------------------------------------------------------------------- 1 | # Pyrogram - Telegram MTProto API Client Library for Python 2 | # Copyright (C) 2017-present Dan 3 | # 4 | # This file is part of Pyrogram. 5 | # 6 | # Pyrogram is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # Pyrogram is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with Pyrogram. If not, see . 18 | 19 | from typing import Callable 20 | 21 | import pyrogram 22 | 23 | import pyrogram_patch 24 | 25 | 26 | class OnUserStatus: 27 | def on_user_status(self=None, filters=None, group: int = 0) -> Callable: 28 | """Decorator for handling user status updates. 29 | This does the same thing as :meth:`~pyrogram.Client.add_handler` using the 30 | :obj:`~pyrogram.handlers.UserStatusHandler`. 31 | 32 | Parameters: 33 | filters (:obj:`~pyrogram.filters`, *optional*): 34 | Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function. 35 | 36 | group (``int``, *optional*): 37 | The group identifier, defaults to 0. 38 | """ 39 | 40 | def decorator(func: Callable) -> Callable: 41 | if isinstance(self, pyrogram_patch.router.Router): 42 | if self._app is not None: 43 | 44 | self._app.add_handler( 45 | pyrogram.handlers.UserStatusHandler(func, filters), group 46 | ) 47 | else: 48 | self._decorators_storage.append((pyrogram.handlers.UserStatusHandler(func, filters), group)) 49 | else: 50 | raise RuntimeError( 51 | "you should only use this in routers, and only as a decorator" 52 | ) 53 | 54 | return func 55 | 56 | return decorator 57 | -------------------------------------------------------------------------------- /pyrogram_patch/router/router.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | 3 | from .patched_decorators import PatchedDecorators 4 | 5 | 6 | class Router(PatchedDecorators): 7 | def __init__(self) -> None: 8 | self._app = None 9 | self._decorators_storage: list = [] 10 | 11 | def set_client(self, client: Client) -> None: 12 | self._app = client 13 | for decorator in self._decorators_storage: 14 | self._app.add_handler( 15 | *decorator 16 | ) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import find_packages, setup 4 | 5 | MINIMAL_PY_VERSION = (3, 8) 6 | if sys.version_info < MINIMAL_PY_VERSION: 7 | raise RuntimeError( 8 | "pyrogram_patch works only with Python {}+".format( 9 | ".".join(map(str, MINIMAL_PY_VERSION)) 10 | ) 11 | ) 12 | 13 | 14 | setup( 15 | name="pyrogram_patch", 16 | version="1.5.0", 17 | license="MIT", 18 | author="kotttee", 19 | python_requires=">=3.8", 20 | description="This package will add middlewares, routers and fsm for pyrogram", 21 | url="https://github.com/kotttee/pyrogram_patch/", 22 | install_requires=[], 23 | classifiers=[ 24 | "License :: MIT License", 25 | "Programming Language :: Python :: 3.8", 26 | "Programming Language :: Python :: 3.9", 27 | "Programming Language :: Python :: 3.10", 28 | "Programming Language :: Python :: 3.11", 29 | ], 30 | packages=find_packages(), 31 | ) 32 | --------------------------------------------------------------------------------