mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-10 10:49:39 +00:00
Completely overhaul errors to be generated dynamically
This commit is contained in:
@@ -1,46 +1,48 @@
|
||||
"""
|
||||
This module holds all the base and automatically generated errors that the
|
||||
Telegram API has. See telethon_generator/errors.json for more.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
from .common import (
|
||||
ReadCancelledError, TypeNotFoundError, InvalidChecksumError,
|
||||
InvalidBufferError, SecurityError, CdnFileTamperedError,
|
||||
BadMessageError, MultiError
|
||||
from ._custom import (
|
||||
ReadCancelledError,
|
||||
TypeNotFoundError,
|
||||
InvalidChecksumError,
|
||||
InvalidBufferError,
|
||||
SecurityError,
|
||||
CdnFileTamperedError,
|
||||
BadMessageError,
|
||||
MultiError,
|
||||
)
|
||||
from ._rpcbase import (
|
||||
RpcError,
|
||||
InvalidDcError,
|
||||
BadRequestError,
|
||||
UnauthorizedError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
AuthKeyError,
|
||||
FloodError,
|
||||
ServerError,
|
||||
BotTimeout,
|
||||
TimedOutError,
|
||||
_mk_error_type
|
||||
)
|
||||
|
||||
# This imports the base errors too, as they're imported there
|
||||
from .rpcbaseerrors import *
|
||||
from .rpcerrorlist import *
|
||||
if sys.version_info < (3, 7):
|
||||
# https://stackoverflow.com/a/7668273/
|
||||
class _TelethonErrors:
|
||||
def __init__(self, _mk_error_type, everything):
|
||||
self._mk_error_type = _mk_error_type
|
||||
self.__dict__.update({
|
||||
k: v
|
||||
for k, v in everything.items()
|
||||
if isinstance(v, type) and issubclass(v, Exception)
|
||||
})
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._mk_error_type(name=name)
|
||||
|
||||
def rpc_message_to_error(rpc_error, request):
|
||||
"""
|
||||
Converts a Telegram's RPC Error to a Python error.
|
||||
sys.modules[__name__] = _TelethonErrors(_mk_error_type, globals())
|
||||
else:
|
||||
# https://www.python.org/dev/peps/pep-0562/
|
||||
def __getattr__(name):
|
||||
return _mk_error_type(name=name)
|
||||
|
||||
:param rpc_error: the RpcError instance.
|
||||
:param request: the request that caused this error.
|
||||
:return: the RPCError as a Python exception that represents this error.
|
||||
"""
|
||||
# Try to get the error by direct look-up, otherwise regex
|
||||
# Case-insensitive, for things like "timeout" which don't conform.
|
||||
cls = rpc_errors_dict.get(rpc_error.error_message.upper(), None)
|
||||
if cls:
|
||||
return cls(request=request)
|
||||
|
||||
for msg_regex, cls in rpc_errors_re:
|
||||
m = re.match(msg_regex, rpc_error.error_message)
|
||||
if m:
|
||||
capture = int(m.group(1)) if m.groups() else None
|
||||
return cls(request=request, capture=capture)
|
||||
|
||||
# Some errors are negative:
|
||||
# * -500 for "No workers running",
|
||||
# * -503 for "Timeout"
|
||||
#
|
||||
# We treat them as if they were positive, so -500 will be treated
|
||||
# as a `ServerError`, etc.
|
||||
cls = base_errors.get(abs(rpc_error.error_code), RPCError)
|
||||
return cls(request=request, message=rpc_error.error_message,
|
||||
code=rpc_error.error_code)
|
||||
del sys
|
||||
|
144
telethon/errors/_rpcbase.py
Normal file
144
telethon/errors/_rpcbase.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import re
|
||||
|
||||
from ._generated import _captures, _descriptions
|
||||
from .. import _tl
|
||||
|
||||
|
||||
_NESTS_QUERY = (
|
||||
_tl.fn.InvokeAfterMsg,
|
||||
_tl.fn.InvokeAfterMsgs,
|
||||
_tl.fn.InitConnection,
|
||||
_tl.fn.InvokeWithLayer,
|
||||
_tl.fn.InvokeWithoutUpdates,
|
||||
_tl.fn.InvokeWithMessagesRange,
|
||||
_tl.fn.InvokeWithTakeout,
|
||||
)
|
||||
|
||||
|
||||
class RpcError(Exception):
|
||||
def __init__(self, code, message, request=None):
|
||||
doc = self.__doc__
|
||||
if doc is None:
|
||||
doc = (
|
||||
'\n Please report this error at https://github.com/LonamiWebs/Telethon/issues/3169'
|
||||
'\n (the library is not aware of it yet and we would appreciate your help, thank you!)'
|
||||
)
|
||||
elif not doc:
|
||||
doc = '(no description available)'
|
||||
|
||||
super().__init__(f'{message}, code={code}{self._fmt_request(request)}: {doc}')
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.request = request
|
||||
# Special-case '2fa' to exclude the 2 from values
|
||||
self.values = [int(x) for x in re.findall(r'-?\d+', re.sub(r'^2fa', '', self.message, flags=re.IGNORECASE))]
|
||||
self.value = self.values[0] if self.values else None
|
||||
|
||||
@staticmethod
|
||||
def _fmt_request(request):
|
||||
if not request:
|
||||
return ''
|
||||
|
||||
n = 0
|
||||
reason = ''
|
||||
while isinstance(request, _NESTS_QUERY):
|
||||
n += 1
|
||||
reason += request.__class__.__name__ + '('
|
||||
request = request.query
|
||||
reason += request.__class__.__name__ + ')' * n
|
||||
|
||||
return ', request={}'.format(reason)
|
||||
|
||||
def __reduce__(self):
|
||||
return type(self), (self.request, self.message, self.code)
|
||||
|
||||
|
||||
def _mk_error_type(*, name=None, code=None, doc=None, _errors={}) -> type:
|
||||
if name is None and code is None:
|
||||
raise ValueError('at least one of `name` or `code` must be provided')
|
||||
|
||||
if name is not None:
|
||||
# Special-case '2fa' to 'twofa'
|
||||
name = re.sub(r'^2fa', 'twofa', name, flags=re.IGNORECASE)
|
||||
|
||||
# Get canonical name
|
||||
name = re.sub(r'[-_\d]', '', name).lower()
|
||||
while name.endswith('error'):
|
||||
name = name[:-len('error')]
|
||||
|
||||
doc = _descriptions.get(name, doc)
|
||||
capture_alias = _captures.get(name)
|
||||
|
||||
d = {'__doc__': doc}
|
||||
|
||||
if capture_alias:
|
||||
d[capture_alias] = property(
|
||||
fget=lambda s: s.value,
|
||||
doc='Alias for `self.value`. Useful to make the code easier to follow.'
|
||||
)
|
||||
|
||||
if (name, None) not in _errors:
|
||||
_errors[(name, None)] = type(f'RpcError{name.title()}', (RpcError,), d)
|
||||
|
||||
if code is not None:
|
||||
# Pretend negative error codes are positive
|
||||
code = str(abs(code))
|
||||
if (None, code) not in _errors:
|
||||
_errors[(None, code)] = type(f'RpcError{code}', (RpcError,), {'__doc__': doc})
|
||||
|
||||
if (name, code) not in _errors:
|
||||
specific = _errors[(name, None)]
|
||||
base = _errors[(None, code)]
|
||||
_errors[(name, code)] = type(f'RpcError{name.title()}{code}', (specific, base), {'__doc__': doc})
|
||||
|
||||
return _errors[(name, code)]
|
||||
|
||||
|
||||
InvalidDcError = _mk_error_type(code=303, doc="""
|
||||
The request must be repeated, but directed to a different data center.
|
||||
""")
|
||||
|
||||
BadRequestError = _mk_error_type(code=400, doc="""
|
||||
The query contains errors. In the event that a request was created
|
||||
using a form and contains user generated data, the user should be
|
||||
notified that the data must be corrected before the query is repeated.
|
||||
""")
|
||||
|
||||
UnauthorizedError = _mk_error_type(code=401, doc="""
|
||||
There was an unauthorized attempt to use functionality available only
|
||||
to authorized users.
|
||||
""")
|
||||
|
||||
ForbiddenError = _mk_error_type(code=403, doc="""
|
||||
Privacy violation. For example, an attempt to write a message to
|
||||
someone who has blacklisted the current user.
|
||||
""")
|
||||
|
||||
NotFoundError = _mk_error_type(code=404, doc="""
|
||||
An attempt to invoke a non-existent object, such as a method.
|
||||
""")
|
||||
|
||||
AuthKeyError = _mk_error_type(code=406, doc="""
|
||||
Errors related to invalid authorization key, like
|
||||
AUTH_KEY_DUPLICATED which can cause the connection to fail.
|
||||
""")
|
||||
|
||||
FloodError = _mk_error_type(code=420, doc="""
|
||||
The maximum allowed number of attempts to invoke the given method
|
||||
with the given input parameters has been exceeded. For example, in an
|
||||
attempt to request a large number of text messages (SMS) for the same
|
||||
phone number.
|
||||
""")
|
||||
|
||||
# Witnessed as -500 for "No workers running"
|
||||
ServerError = _mk_error_type(code=500, doc="""
|
||||
An internal server error occurred while a request was being processed
|
||||
for example, there was a disruption while accessing a database or file
|
||||
storage.
|
||||
""")
|
||||
|
||||
# Witnessed as -503 for "Timeout"
|
||||
BotTimeout = TimedOutError = _mk_error_type(code=503, doc="""
|
||||
Clicking the inline buttons of bots that never (or take to long to)
|
||||
call ``answerCallbackQuery`` will result in this "special" RPCError.
|
||||
""")
|
@@ -1,131 +0,0 @@
|
||||
from .. import _tl
|
||||
|
||||
_NESTS_QUERY = (
|
||||
_tl.fn.InvokeAfterMsg,
|
||||
_tl.fn.InvokeAfterMsgs,
|
||||
_tl.fn.InitConnection,
|
||||
_tl.fn.InvokeWithLayer,
|
||||
_tl.fn.InvokeWithoutUpdates,
|
||||
_tl.fn.InvokeWithMessagesRange,
|
||||
_tl.fn.InvokeWithTakeout,
|
||||
)
|
||||
|
||||
class RPCError(Exception):
|
||||
"""Base class for all Remote Procedure Call errors."""
|
||||
code = None
|
||||
message = None
|
||||
|
||||
def __init__(self, request, message, code=None):
|
||||
super().__init__('RPCError {}: {}{}'.format(
|
||||
code or self.code, message, self._fmt_request(request)))
|
||||
|
||||
self.request = request
|
||||
self.code = code
|
||||
self.message = message
|
||||
|
||||
@staticmethod
|
||||
def _fmt_request(request):
|
||||
n = 0
|
||||
reason = ''
|
||||
while isinstance(request, _NESTS_QUERY):
|
||||
n += 1
|
||||
reason += request.__class__.__name__ + '('
|
||||
request = request.query
|
||||
reason += request.__class__.__name__ + ')' * n
|
||||
|
||||
return ' (caused by {})'.format(reason)
|
||||
|
||||
def __reduce__(self):
|
||||
return type(self), (self.request, self.message, self.code)
|
||||
|
||||
|
||||
class InvalidDCError(RPCError):
|
||||
"""
|
||||
The request must be repeated, but directed to a different data center.
|
||||
"""
|
||||
code = 303
|
||||
message = 'ERROR_SEE_OTHER'
|
||||
|
||||
|
||||
class BadRequestError(RPCError):
|
||||
"""
|
||||
The query contains errors. In the event that a request was created
|
||||
using a form and contains user generated data, the user should be
|
||||
notified that the data must be corrected before the query is repeated.
|
||||
"""
|
||||
code = 400
|
||||
message = 'BAD_REQUEST'
|
||||
|
||||
|
||||
class UnauthorizedError(RPCError):
|
||||
"""
|
||||
There was an unauthorized attempt to use functionality available only
|
||||
to authorized users.
|
||||
"""
|
||||
code = 401
|
||||
message = 'UNAUTHORIZED'
|
||||
|
||||
|
||||
class ForbiddenError(RPCError):
|
||||
"""
|
||||
Privacy violation. For example, an attempt to write a message to
|
||||
someone who has blacklisted the current user.
|
||||
"""
|
||||
code = 403
|
||||
message = 'FORBIDDEN'
|
||||
|
||||
|
||||
class NotFoundError(RPCError):
|
||||
"""
|
||||
An attempt to invoke a non-existent object, such as a method.
|
||||
"""
|
||||
code = 404
|
||||
message = 'NOT_FOUND'
|
||||
|
||||
|
||||
class AuthKeyError(RPCError):
|
||||
"""
|
||||
Errors related to invalid authorization key, like
|
||||
AUTH_KEY_DUPLICATED which can cause the connection to fail.
|
||||
"""
|
||||
code = 406
|
||||
message = 'AUTH_KEY'
|
||||
|
||||
|
||||
class FloodError(RPCError):
|
||||
"""
|
||||
The maximum allowed number of attempts to invoke the given method
|
||||
with the given input parameters has been exceeded. For example, in an
|
||||
attempt to request a large number of text messages (SMS) for the same
|
||||
phone number.
|
||||
"""
|
||||
code = 420
|
||||
message = 'FLOOD'
|
||||
|
||||
|
||||
class ServerError(RPCError):
|
||||
"""
|
||||
An internal server error occurred while a request was being processed
|
||||
for example, there was a disruption while accessing a database or file
|
||||
storage.
|
||||
"""
|
||||
code = 500 # Also witnessed as -500
|
||||
message = 'INTERNAL'
|
||||
|
||||
|
||||
class TimedOutError(RPCError):
|
||||
"""
|
||||
Clicking the inline buttons of bots that never (or take to long to)
|
||||
call ``answerCallbackQuery`` will result in this "special" RPCError.
|
||||
"""
|
||||
code = 503 # Only witnessed as -503
|
||||
message = 'Timeout'
|
||||
|
||||
|
||||
BotTimeout = TimedOutError
|
||||
|
||||
|
||||
base_errors = {x.code: x for x in (
|
||||
InvalidDCError, BadRequestError, UnauthorizedError, ForbiddenError,
|
||||
NotFoundError, AuthKeyError, FloodError, ServerError, TimedOutError
|
||||
)}
|
Reference in New Issue
Block a user