mirror of
https://github.com/Mayuri-Chan/pyrofork.git
synced 2026-01-03 14:04:51 +00:00
pyrofork: Add Error handler
Signed-off-by: Yasir Aris M <git@yasirdev.my.id>
This commit is contained in:
parent
bd46ff3977
commit
481817649b
8 changed files with 264 additions and 64 deletions
|
|
@ -82,3 +82,4 @@ Details
|
||||||
.. autodecorator:: pyrogram.Client.on_poll()
|
.. autodecorator:: pyrogram.Client.on_poll()
|
||||||
.. autodecorator:: pyrogram.Client.on_disconnect()
|
.. autodecorator:: pyrogram.Client.on_disconnect()
|
||||||
.. autodecorator:: pyrogram.Client.on_raw_update()
|
.. autodecorator:: pyrogram.Client.on_raw_update()
|
||||||
|
.. autodecorator:: pyrogram.Client.on_error()
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,8 @@ import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
from pyrogram import errors
|
from pyrogram import errors, raw, types, utils
|
||||||
from pyrogram import utils
|
from pyrogram.handlers.handler import Handler
|
||||||
from pyrogram import raw
|
|
||||||
from pyrogram.handlers import (
|
from pyrogram.handlers import (
|
||||||
BotBusinessConnectHandler,
|
BotBusinessConnectHandler,
|
||||||
BotBusinessMessageHandler,
|
BotBusinessMessageHandler,
|
||||||
|
|
@ -33,6 +32,7 @@ from pyrogram.handlers import (
|
||||||
MessageHandler,
|
MessageHandler,
|
||||||
EditedMessageHandler,
|
EditedMessageHandler,
|
||||||
EditedBotBusinessMessageHandler,
|
EditedBotBusinessMessageHandler,
|
||||||
|
ErrorHandler,
|
||||||
DeletedMessagesHandler,
|
DeletedMessagesHandler,
|
||||||
DeletedBotBusinessMessagesHandler,
|
DeletedBotBusinessMessagesHandler,
|
||||||
MessageReactionUpdatedHandler,
|
MessageReactionUpdatedHandler,
|
||||||
|
|
@ -97,6 +97,7 @@ class Dispatcher:
|
||||||
|
|
||||||
self.handler_worker_tasks = []
|
self.handler_worker_tasks = []
|
||||||
self.locks_list = []
|
self.locks_list = []
|
||||||
|
self.error_handlers = []
|
||||||
|
|
||||||
self.updates_queue = asyncio.Queue()
|
self.updates_queue = asyncio.Queue()
|
||||||
self.groups = OrderedDict()
|
self.groups = OrderedDict()
|
||||||
|
|
@ -286,6 +287,7 @@ class Dispatcher:
|
||||||
|
|
||||||
self.handler_worker_tasks.clear()
|
self.handler_worker_tasks.clear()
|
||||||
self.groups.clear()
|
self.groups.clear()
|
||||||
|
self.error_handlers.clear()
|
||||||
|
|
||||||
log.info("Stopped %s HandlerTasks", self.client.workers)
|
log.info("Stopped %s HandlerTasks", self.client.workers)
|
||||||
|
|
||||||
|
|
@ -295,11 +297,14 @@ class Dispatcher:
|
||||||
await lock.acquire()
|
await lock.acquire()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if group not in self.groups:
|
if isinstance(handler, ErrorHandler):
|
||||||
self.groups[group] = []
|
if handler not in self.error_handlers:
|
||||||
self.groups = OrderedDict(sorted(self.groups.items()))
|
self.error_handlers.append(handler)
|
||||||
|
else:
|
||||||
self.groups[group].append(handler)
|
if group not in self.groups:
|
||||||
|
self.groups[group] = []
|
||||||
|
self.groups = OrderedDict(sorted(self.groups.items()))
|
||||||
|
self.groups[group].append(handler)
|
||||||
finally:
|
finally:
|
||||||
for lock in self.locks_list:
|
for lock in self.locks_list:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
@ -312,76 +317,94 @@ class Dispatcher:
|
||||||
await lock.acquire()
|
await lock.acquire()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if group not in self.groups:
|
if isinstance(handler, ErrorHandler):
|
||||||
raise ValueError(f"Group {group} does not exist. Handler was not removed.")
|
if handler not in self.error_handlers:
|
||||||
|
raise ValueError(
|
||||||
self.groups[group].remove(handler)
|
f"Error handler {handler} does not exist. Handler was not removed."
|
||||||
|
)
|
||||||
|
self.error_handlers.remove(handler)
|
||||||
|
else:
|
||||||
|
if group not in self.groups:
|
||||||
|
raise ValueError(f"Group {group} does not exist. Handler was not removed.")
|
||||||
|
self.groups[group].remove(handler)
|
||||||
finally:
|
finally:
|
||||||
for lock in self.locks_list:
|
for lock in self.locks_list:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
self.loop.create_task(fn())
|
self.loop.create_task(fn())
|
||||||
|
|
||||||
async def handler_worker(self, lock):
|
async def handler_worker(self, lock: asyncio.Lock):
|
||||||
while True:
|
while True:
|
||||||
packet = await self.updates_queue.get()
|
packet = await self.updates_queue.get()
|
||||||
|
|
||||||
if packet is None:
|
if packet is None:
|
||||||
break
|
break
|
||||||
|
await self._process_packet(packet, lock)
|
||||||
|
|
||||||
try:
|
async def _process_packet(
|
||||||
update, users, chats = packet
|
self,
|
||||||
parser = self.update_parsers.get(type(update), None)
|
packet: tuple[raw.core.TLObject, dict[int, types.Update], dict[int, types.Update]],
|
||||||
|
lock: asyncio.Lock,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
update, users, chats = packet
|
||||||
|
parser = self.update_parsers.get(type(update))
|
||||||
|
|
||||||
parsed_update, handler_type = (
|
if parser is not None:
|
||||||
await parser(update, users, chats)
|
parsed_result = parser(update, users, chats)
|
||||||
if parser is not None
|
if inspect.isawaitable(parsed_result):
|
||||||
else (None, type(None))
|
parsed_update, handler_type = await parsed_result
|
||||||
)
|
else:
|
||||||
|
parsed_update, handler_type = parsed_result
|
||||||
async with lock:
|
else:
|
||||||
for group in self.groups.values():
|
parsed_update, handler_type = (None, type(None))
|
||||||
for handler in group:
|
|
||||||
args = None
|
|
||||||
|
|
||||||
if isinstance(handler, handler_type):
|
|
||||||
try:
|
|
||||||
if await handler.check(self.client, parsed_update):
|
|
||||||
args = (parsed_update,)
|
|
||||||
except Exception as e:
|
|
||||||
log.exception(e)
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
async with lock:
|
||||||
|
for group in self.groups.values():
|
||||||
|
for handler in group:
|
||||||
|
try:
|
||||||
|
if parsed_update is not None:
|
||||||
|
if isinstance(handler, handler_type) and await handler.check(
|
||||||
|
self.client, parsed_update
|
||||||
|
):
|
||||||
|
await self._execute_callback(handler, parsed_update)
|
||||||
|
break
|
||||||
elif isinstance(handler, RawUpdateHandler):
|
elif isinstance(handler, RawUpdateHandler):
|
||||||
try:
|
await self._execute_callback(handler, update, users, chats)
|
||||||
if await handler.check(self.client, update):
|
break
|
||||||
args = (update, users, chats)
|
except (pyrogram.StopPropagation, pyrogram.ContinuePropagation) as e:
|
||||||
except Exception as e:
|
if isinstance(e, pyrogram.StopPropagation):
|
||||||
log.exception(e)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if args is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
if inspect.iscoroutinefunction(handler.callback):
|
|
||||||
await handler.callback(self.client, *args)
|
|
||||||
else:
|
|
||||||
await self.loop.run_in_executor(
|
|
||||||
self.client.executor,
|
|
||||||
handler.callback,
|
|
||||||
self.client,
|
|
||||||
*args
|
|
||||||
)
|
|
||||||
except pyrogram.StopPropagation:
|
|
||||||
raise
|
raise
|
||||||
except pyrogram.ContinuePropagation:
|
except Exception as exception:
|
||||||
continue
|
if parsed_update is not None:
|
||||||
except Exception as e:
|
await self._handle_exception(parsed_update, exception)
|
||||||
log.exception(e)
|
except pyrogram.StopPropagation:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(e)
|
||||||
|
finally:
|
||||||
|
self.updates_queue.task_done()
|
||||||
|
|
||||||
break
|
async def _handle_exception(self, parsed_update: types.Update, exception: Exception):
|
||||||
|
handled_error = False
|
||||||
|
for error_handler in self.error_handlers:
|
||||||
|
try:
|
||||||
|
if await error_handler.check(self.client, parsed_update, exception):
|
||||||
|
handled_error = True
|
||||||
|
break
|
||||||
except pyrogram.StopPropagation:
|
except pyrogram.StopPropagation:
|
||||||
pass
|
raise
|
||||||
except Exception as e:
|
except pyrogram.ContinuePropagation:
|
||||||
log.exception(e)
|
continue
|
||||||
|
except Exception as inner_exception:
|
||||||
|
log.exception("Error in error handler: %s", inner_exception)
|
||||||
|
|
||||||
|
if not handled_error:
|
||||||
|
log.exception("Unhandled exception: %s", exception)
|
||||||
|
|
||||||
|
async def _execute_callback(self, handler: Handler, *args):
|
||||||
|
if inspect.iscoroutinefunction(handler.callback):
|
||||||
|
await handler.callback(self.client, *args)
|
||||||
|
else:
|
||||||
|
await self.client.loop.run_in_executor(
|
||||||
|
self.client.executor, handler.callback, self.client, *args
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ from .deleted_bot_business_messages_handler import DeletedBotBusinessMessagesHan
|
||||||
from .disconnect_handler import DisconnectHandler
|
from .disconnect_handler import DisconnectHandler
|
||||||
from .edited_message_handler import EditedMessageHandler
|
from .edited_message_handler import EditedMessageHandler
|
||||||
from .edited_bot_business_message_handler import EditedBotBusinessMessageHandler
|
from .edited_bot_business_message_handler import EditedBotBusinessMessageHandler
|
||||||
|
from .error_handler import ErrorHandler
|
||||||
from .inline_query_handler import InlineQueryHandler
|
from .inline_query_handler import InlineQueryHandler
|
||||||
from .message_handler import MessageHandler
|
from .message_handler import MessageHandler
|
||||||
from .poll_handler import PollHandler
|
from .poll_handler import PollHandler
|
||||||
|
|
|
||||||
79
pyrogram/handlers/error_handler.py
Normal file
79
pyrogram/handlers/error_handler.py
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||||
|
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrofork.
|
||||||
|
#
|
||||||
|
# Pyrofork is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrofork is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from .handler import Handler
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.types import Update
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorHandler(Handler):
|
||||||
|
"""The Error handler class. Used to handle errors.
|
||||||
|
It is intended to be used with :meth:`~pyrogram.Client.add_handler`
|
||||||
|
|
||||||
|
For a nicer way to register this handler, have a look at the
|
||||||
|
:meth:`~pyrogram.Client.on_error` decorator.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
callback (``Callable``):
|
||||||
|
Pass a function that will be called when a new Error arrives. It takes *(client, error)*
|
||||||
|
as positional arguments (look at the section below for a detailed description).
|
||||||
|
|
||||||
|
exceptions (``Exception`` | Iterable of ``Exception``, *optional*):
|
||||||
|
Pass one or more exception classes to allow only a subset of errors to be passed
|
||||||
|
in your callback function.
|
||||||
|
|
||||||
|
Other parameters:
|
||||||
|
client (:obj:`~pyrogram.Client`):
|
||||||
|
The Client itself, useful when you want to call other API methods inside the error handler.
|
||||||
|
|
||||||
|
update (:obj:`~pyrogram.Update`):
|
||||||
|
The update that caused the error.
|
||||||
|
|
||||||
|
error (``Exception``):
|
||||||
|
The error that was raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
callback: Callable,
|
||||||
|
exceptions: type[Exception] | Iterable[type[Exception]] | None = None,
|
||||||
|
):
|
||||||
|
self.exceptions = (
|
||||||
|
tuple(exceptions)
|
||||||
|
if isinstance(exceptions, Iterable)
|
||||||
|
else (exceptions,)
|
||||||
|
if exceptions
|
||||||
|
else (Exception,)
|
||||||
|
)
|
||||||
|
super().__init__(callback)
|
||||||
|
|
||||||
|
async def check(self, client: pyrogram.Client, update: Update, exception: Exception) -> bool:
|
||||||
|
if isinstance(exception, self.exceptions):
|
||||||
|
await self.callback(client, update, exception)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_remove(self, error: type[Exception] | Iterable[type[Exception]]) -> bool:
|
||||||
|
return isinstance(error, self.exceptions)
|
||||||
|
|
@ -28,6 +28,7 @@ from .on_deleted_bot_business_messages import OnDeletedBotBusinessMessages
|
||||||
from .on_disconnect import OnDisconnect
|
from .on_disconnect import OnDisconnect
|
||||||
from .on_edited_message import OnEditedMessage
|
from .on_edited_message import OnEditedMessage
|
||||||
from .on_edited_bot_business_message import OnEditedBotBusinessMessage
|
from .on_edited_bot_business_message import OnEditedBotBusinessMessage
|
||||||
|
from .on_error import OnError
|
||||||
from .on_inline_query import OnInlineQuery
|
from .on_inline_query import OnInlineQuery
|
||||||
from .on_message import OnMessage
|
from .on_message import OnMessage
|
||||||
from .on_poll import OnPoll
|
from .on_poll import OnPoll
|
||||||
|
|
@ -47,6 +48,7 @@ class Decorators(
|
||||||
OnBotBusinessMessage,
|
OnBotBusinessMessage,
|
||||||
OnEditedMessage,
|
OnEditedMessage,
|
||||||
OnEditedBotBusinessMessage,
|
OnEditedBotBusinessMessage,
|
||||||
|
OnError,
|
||||||
OnDeletedMessages,
|
OnDeletedMessages,
|
||||||
OnDeletedBotBusinessMessages,
|
OnDeletedBotBusinessMessages,
|
||||||
OnCallbackQuery,
|
OnCallbackQuery,
|
||||||
|
|
|
||||||
50
pyrogram/methods/decorators/on_error.py
Normal file
50
pyrogram/methods/decorators/on_error.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||||
|
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrofork.
|
||||||
|
#
|
||||||
|
# Pyrofork is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrofork is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.filters import Filter
|
||||||
|
|
||||||
|
|
||||||
|
class OnError:
|
||||||
|
def on_error(self=None, errors=None) -> Callable:
|
||||||
|
"""Decorator for handling new errors.
|
||||||
|
|
||||||
|
This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
|
||||||
|
:obj:`~pyrogram.handlers.MessageHandler`.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
errors (:obj:`~Exception`, *optional*):
|
||||||
|
Pass one or more errors to allow only a subset of errors to be passed
|
||||||
|
in your function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func: Callable) -> Callable:
|
||||||
|
if isinstance(self, pyrogram.Client):
|
||||||
|
self.add_handler(pyrogram.handlers.ErrorHandler(func, errors), 0)
|
||||||
|
elif isinstance(self, Filter) or self is None:
|
||||||
|
if not hasattr(func, "handlers"):
|
||||||
|
func.handlers = []
|
||||||
|
|
||||||
|
func.handlers.append((pyrogram.handlers.ErrorHandler(func, self), 0))
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
from .add_handler import AddHandler
|
from .add_handler import AddHandler
|
||||||
from .export_session_string import ExportSessionString
|
from .export_session_string import ExportSessionString
|
||||||
from .remove_handler import RemoveHandler
|
from .remove_handler import RemoveHandler
|
||||||
|
from .remove_error_handler import RemoveErrorHandler
|
||||||
from .restart import Restart
|
from .restart import Restart
|
||||||
from .run import Run
|
from .run import Run
|
||||||
from .run_sync import RunSync
|
from .run_sync import RunSync
|
||||||
|
|
@ -32,6 +33,7 @@ class Utilities(
|
||||||
AddHandler,
|
AddHandler,
|
||||||
ExportSessionString,
|
ExportSessionString,
|
||||||
RemoveHandler,
|
RemoveHandler,
|
||||||
|
RemoveErrorHandler,
|
||||||
Restart,
|
Restart,
|
||||||
Run,
|
Run,
|
||||||
RunSync,
|
RunSync,
|
||||||
|
|
|
||||||
42
pyrogram/methods/utilities/remove_error_handler.py
Normal file
42
pyrogram/methods/utilities/remove_error_handler.py
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||||
|
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||||
|
#
|
||||||
|
# This file is part of Pyrofork.
|
||||||
|
#
|
||||||
|
# Pyrofork is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Pyrofork is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
from collections.abc import Iterable
|
||||||
|
import pyrogram
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveErrorHandler:
|
||||||
|
def remove_error_handler(
|
||||||
|
self: pyrogram.Client,
|
||||||
|
exception: type[Exception] | Iterable[type[Exception]] = Exception,
|
||||||
|
):
|
||||||
|
"""Remove a previously registered error handler using exception classes.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
exception (``Exception`` | Iterable of ``Exception``, *optional*):
|
||||||
|
The error(s) for handlers to be removed. Defaults to Exception.
|
||||||
|
"""
|
||||||
|
to_remove = [
|
||||||
|
handler
|
||||||
|
for handler in self.dispatcher.error_handlers
|
||||||
|
if handler.check_remove(exception)
|
||||||
|
]
|
||||||
|
for handler in to_remove:
|
||||||
|
self.dispatcher.error_handlers.remove(handler)
|
||||||
Loading…
Reference in a new issue