mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-29 04:43:45 +03:00
Completely overhaul errors to be generated dynamically
This commit is contained in:
parent
cfe47a0434
commit
debde6e856
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,7 +2,7 @@
|
|||
/telethon/_tl/fn/
|
||||
/telethon/_tl/*.py
|
||||
/telethon/_tl/alltlobjects.py
|
||||
/telethon/errors/rpcerrorlist.py
|
||||
/telethon/errors/_generated.py
|
||||
|
||||
# User session
|
||||
*.session
|
||||
|
|
|
@ -140,6 +140,72 @@ for the library to work properly. If you still don't want it, you should subclas
|
|||
override the methods to do nothing.
|
||||
|
||||
|
||||
Complete overhaul of errors
|
||||
---------------------------
|
||||
|
||||
The following error name have changed to follow a better naming convention (clearer acronyms):
|
||||
|
||||
* ``RPCError`` is now ``RpcError``.
|
||||
* ``InvalidDCError`` is now ``InvalidDcError`` (lowercase ``c``).
|
||||
|
||||
The base errors no longer have a ``.message`` field at the class-level. Instead, it is now an
|
||||
attribute at the instance level (meaning you cannot do ``BadRequestError.message``, it must be
|
||||
``bad_request_err.message`` where ``isinstance(bad_request_err, BadRequestError)``).
|
||||
|
||||
The ``.message`` will gain its value at the time the error is constructed, rather than being
|
||||
known beforehand.
|
||||
|
||||
The parameter order for ``RpcError`` and all its subclasses are now ``(code, message, request)``,
|
||||
as opposed to ``(message, request, code)``.
|
||||
|
||||
Because Telegram errors can be added at any time, the library no longer generate a fixed set of
|
||||
them. This means you can no longer use ``dir`` to get a full list of them. Instead, the errors
|
||||
are automatically generated depending on the name you use for the error, with the following rules:
|
||||
|
||||
* Numbers are removed from the name. The Telegram error ``FLOOD_WAIT_42`` is transformed into
|
||||
``FLOOD_WAIT_``.
|
||||
* Underscores are removed from the name. ``FLOOD_WAIT_`` becomes ``FLOODWAIT``.
|
||||
* Everything is lowercased. ``FLOODWAIT`` turns into ``floodwait``.
|
||||
* While the name ends with ``error``, this suffix is removed.
|
||||
|
||||
The only exception to this rule is ``2FA_CONFIRM_WAIT_0``, which is transformed as
|
||||
``twofaconfirmwait`` (read as ``TwoFaConfirmWait``).
|
||||
|
||||
What all this means is that, if Telegram raises a ``FLOOD_WAIT_42``, you can write the following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from telethon.errors import FloodWaitError
|
||||
|
||||
try:
|
||||
await client.send_message(chat, message)
|
||||
except FloodWaitError as e:
|
||||
print(f'Flood! wait for {e.seconds} seconds')
|
||||
|
||||
Essentially, old code will keep working, but now you have the freedom to define even yet-to-be
|
||||
discovered errors. This makes use of `PEP 562 <https://www.python.org/dev/peps/pep-0562/>`__ on
|
||||
Python 3.7 and above and a more-hacky approach below (which your IDE may not love).
|
||||
|
||||
Given the above rules, you could also write ``except errors.FLOOD_WAIT`` if you prefer to match
|
||||
Telegram's naming conventions. We recommend Camel-Case naming with the "Error" suffix, but that's
|
||||
up to you.
|
||||
|
||||
All errors will include a list of ``.values`` (the extracted number) and ``.value`` (the first
|
||||
number extracted, or ``None`` if ``values`` is empty). In addition to that, certain errors have
|
||||
a more-recognizable alias (such as ``FloodWait`` which has ``.seconds`` for its ``.value``).
|
||||
|
||||
The ``telethon.errors`` module continues to provide certain predefined ``RpcError`` to match on
|
||||
the *code* of the error and not its message (for instance, match all errors with code 403 with
|
||||
``ForbiddenError``). Note that a certain error message can appear with different codes too, this
|
||||
is decided by Telegram.
|
||||
|
||||
The ``telethon.errors`` module continues to provide custom errors used by the library such as
|
||||
``TypeNotFoundError``.
|
||||
|
||||
// TODO keep RPCError around? eh idk how much it's used
|
||||
// TODO should RpcError subclass ValueError? technically the values used in the request somehow were wrong…
|
||||
// TODO provide a way to see which errors are known in the docs or at tl.telethon.dev
|
||||
|
||||
The "iter" variant of the client methods have been removed
|
||||
----------------------------------------------------------
|
||||
|
||||
|
@ -385,6 +451,8 @@ However, you're encouraged to change uses of ``.raw_text`` with ``.message``, an
|
|||
either ``.md_text`` or ``.html_text`` as needed. This is because both ``.text`` and ``.raw_text``
|
||||
may disappear in future versions, and their behaviour is not immediately obvious.
|
||||
|
||||
// TODO actually provide the things mentioned here
|
||||
|
||||
|
||||
Using a flat list to define buttons will now create rows and not columns
|
||||
------------------------------------------------------------------------
|
||||
|
|
2
setup.py
2
setup.py
|
@ -47,7 +47,7 @@ GENERATOR_DIR = Path('telethon_generator')
|
|||
LIBRARY_DIR = Path('telethon')
|
||||
|
||||
ERRORS_IN = GENERATOR_DIR / 'data/errors.csv'
|
||||
ERRORS_OUT = LIBRARY_DIR / 'errors/rpcerrorlist.py'
|
||||
ERRORS_OUT = LIBRARY_DIR / 'errors/_generated.py'
|
||||
|
||||
METHODS_IN = GENERATOR_DIR / 'data/methods.csv'
|
||||
|
||||
|
|
|
@ -676,7 +676,7 @@ async def get_permissions(
|
|||
for participant in chat.full_chat.participants.participants:
|
||||
if participant.user_id == user.user_id:
|
||||
return _custom.ParticipantPermissions(participant, True)
|
||||
raise errors.UserNotParticipantError(None)
|
||||
raise errors.USER_NOT_PARTICIPANT(400, 'USER_NOT_PARTICIPANT')
|
||||
|
||||
raise ValueError('You must pass either a channel or a chat')
|
||||
|
||||
|
@ -694,7 +694,7 @@ async def get_stats(
|
|||
try:
|
||||
req = _tl.fn.stats.GetMessageStats(entity, message)
|
||||
return await self(req)
|
||||
except errors.StatsMigrateError as e:
|
||||
except errors.STATS_MIGRATE as e:
|
||||
dc = e.dc
|
||||
else:
|
||||
# Don't bother fetching the Channel entity (costs a request), instead
|
||||
|
@ -703,13 +703,13 @@ async def get_stats(
|
|||
try:
|
||||
req = _tl.fn.stats.GetBroadcastStats(entity)
|
||||
return await self(req)
|
||||
except errors.StatsMigrateError as e:
|
||||
except errors.STATS_MIGRATE as e:
|
||||
dc = e.dc
|
||||
except errors.BroadcastRequiredError:
|
||||
except errors.BROADCAST_REQUIRED:
|
||||
req = _tl.fn.stats.GetMegagroupStats(entity)
|
||||
try:
|
||||
return await self(req)
|
||||
except errors.StatsMigrateError as e:
|
||||
except errors.STATS_MIGRATE as e:
|
||||
dc = e.dc
|
||||
|
||||
sender = await self._borrow_exported_sender(dc)
|
||||
|
|
|
@ -232,7 +232,7 @@ async def delete_dialog(
|
|||
result = await self(_tl.fn.messages.DeleteChatUser(
|
||||
entity.chat_id, _tl.InputUserSelf(), revoke_history=revoke
|
||||
))
|
||||
except errors.PeerIdInvalidError:
|
||||
except errors.PEER_ID_INVALID:
|
||||
# Happens if we didn't have the deactivated information
|
||||
result = None
|
||||
else:
|
||||
|
|
|
@ -12,8 +12,8 @@ from .. import version, helpers, __name__ as __base_name__, _tl
|
|||
from .._crypto import rsa
|
||||
from .._misc import markdown, entitycache, statecache, enums
|
||||
from .._network import MTProtoSender, Connection, ConnectionTcpFull, connection as conns
|
||||
from ..sessions import Session, SQLiteSession, MemorySession
|
||||
from ..sessions.types import DataCenter, SessionState
|
||||
from .._sessions import Session, SQLiteSession, MemorySession
|
||||
from .._sessions.types import DataCenter, SessionState
|
||||
|
||||
DEFAULT_DC_ID = 2
|
||||
DEFAULT_IPV4_IP = '149.154.167.51'
|
||||
|
|
|
@ -8,7 +8,8 @@ import traceback
|
|||
import typing
|
||||
import logging
|
||||
|
||||
from .. import events, utils, errors, _tl
|
||||
from .. import events, utils, _tl
|
||||
from ..errors._rpcbase import RpcError
|
||||
from ..events.common import EventBuilder, EventCommon
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
|
@ -244,7 +245,7 @@ async def _dispatch_update(self: 'TelegramClient', update, others, channel_id, p
|
|||
await _get_difference(self, update, channel_id, pts_date)
|
||||
except OSError:
|
||||
pass # We were disconnected, that's okay
|
||||
except errors.RPCError:
|
||||
except RpcError:
|
||||
# There's a high chance the request fails because we lack
|
||||
# the channel. Because these "happen sporadically" (#1428)
|
||||
# we should be okay (no flood waits) even if more occur.
|
||||
|
@ -418,7 +419,7 @@ async def _handle_auto_reconnect(self: 'TelegramClient'):
|
|||
await self.catch_up()
|
||||
|
||||
self._log[__name__].info('Successfully fetched missed updates')
|
||||
except errors.RPCError as e:
|
||||
except RpcError as e:
|
||||
self._log[__name__].warning('Failed to get missed updates after '
|
||||
'reconnect: %r', e)
|
||||
except Exception:
|
||||
|
|
|
@ -4,10 +4,11 @@ import itertools
|
|||
import time
|
||||
import typing
|
||||
|
||||
from ..errors._custom import MultiError
|
||||
from ..errors._rpcbase import RpcError, ServerError, FloodError, InvalidDcError, UnauthorizedError
|
||||
from .. import errors, hints, _tl
|
||||
from .._misc import helpers, utils
|
||||
from ..errors import MultiError, RPCError
|
||||
from ..sessions.types import Entity
|
||||
from .._sessions.types import Entity
|
||||
|
||||
_NOT_A_REQUEST = lambda: TypeError('You can only invoke requests, not types!')
|
||||
|
||||
|
@ -49,7 +50,7 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
await asyncio.sleep(diff)
|
||||
self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None)
|
||||
else:
|
||||
raise errors.FloodWaitError(request=r, capture=diff)
|
||||
raise errors.FLOOD_WAIT(420, f'FLOOD_WAIT_{diff}', request=r)
|
||||
|
||||
if self._no_updates:
|
||||
r = _tl.fn.InvokeWithoutUpdates(r)
|
||||
|
@ -67,7 +68,7 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
for f in future:
|
||||
try:
|
||||
result = await f
|
||||
except RPCError as e:
|
||||
except RpcError as e:
|
||||
exceptions.append(e)
|
||||
results.append(None)
|
||||
continue
|
||||
|
@ -87,22 +88,20 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
if entities:
|
||||
await self.session.insert_entities(entities)
|
||||
return result
|
||||
except (errors.ServerError, errors.RpcCallFailError,
|
||||
errors.RpcMcgetFailError, errors.InterdcCallErrorError,
|
||||
errors.InterdcCallRichErrorError) as e:
|
||||
except ServerError as e:
|
||||
last_error = e
|
||||
self._log[__name__].warning(
|
||||
'Telegram is having internal issues %s: %s',
|
||||
e.__class__.__name__, e)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
except (errors.FloodWaitError, errors.SlowModeWaitError, errors.FloodTestPhoneWaitError) as e:
|
||||
except FloodError as e:
|
||||
last_error = e
|
||||
if utils.is_list_like(request):
|
||||
request = request[request_index]
|
||||
|
||||
# SLOW_MODE_WAIT is chat-specific, not request-specific
|
||||
if not isinstance(e, errors.SlowModeWaitError):
|
||||
# SLOWMODE_WAIT is chat-specific, not request-specific
|
||||
if not isinstance(e, errors.SLOWMODE_WAIT):
|
||||
self._flood_waited_requests\
|
||||
[request.CONSTRUCTOR_ID] = time.time() + e.seconds
|
||||
|
||||
|
@ -116,12 +115,11 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
|
|||
await asyncio.sleep(e.seconds)
|
||||
else:
|
||||
raise
|
||||
except (errors.PhoneMigrateError, errors.NetworkMigrateError,
|
||||
errors.UserMigrateError) as e:
|
||||
except InvalidDcError as e:
|
||||
last_error = e
|
||||
self._log[__name__].info('Phone migrated to %d', e.new_dc)
|
||||
should_raise = isinstance(e, (
|
||||
errors.PhoneMigrateError, errors.NetworkMigrateError
|
||||
errors.PHONE_MIGRATE, errors.NETWORK_MIGRATE
|
||||
))
|
||||
if should_raise and await self.is_user_authorized():
|
||||
raise
|
||||
|
@ -138,7 +136,7 @@ async def get_me(self: 'TelegramClient', input_peer: bool = False) \
|
|||
try:
|
||||
me = (await self(_tl.fn.users.GetUsers([_tl.InputUserSelf()])))[0]
|
||||
return utils.get_input_peer(me, allow_self=False) if input_peer else me
|
||||
except errors.UnauthorizedError:
|
||||
except UnauthorizedError:
|
||||
return None
|
||||
|
||||
async def is_bot(self: 'TelegramClient') -> bool:
|
||||
|
@ -150,7 +148,7 @@ async def is_user_authorized(self: 'TelegramClient') -> bool:
|
|||
# Any request that requires authorization will work
|
||||
await self(_tl.fn.updates.GetState())
|
||||
self._authorized = True
|
||||
except errors.RPCError:
|
||||
except RpcError:
|
||||
self._authorized = False
|
||||
|
||||
return self._authorized
|
||||
|
@ -290,7 +288,7 @@ async def get_input_entity(
|
|||
channels = await self(_tl.fn.channels.GetChannels([
|
||||
_tl.InputChannel(peer.channel_id, access_hash=0)]))
|
||||
return utils.get_input_peer(channels.chats[0])
|
||||
except errors.ChannelInvalidError:
|
||||
except errors.CHANNEL_INVALID:
|
||||
pass
|
||||
|
||||
raise ValueError(
|
||||
|
@ -338,7 +336,7 @@ async def _get_entity_from_string(self: 'TelegramClient', string):
|
|||
_tl.fn.contacts.GetContacts(0))).users:
|
||||
if user.phone == phone:
|
||||
return user
|
||||
except errors.BotMethodInvalidError:
|
||||
except errors.BOT_METHOD_INVALID:
|
||||
raise ValueError('Cannot get entity by phone number as a '
|
||||
'bot (try using integer IDs, not strings)')
|
||||
elif string.lower() in ('me', 'self'):
|
||||
|
@ -360,7 +358,7 @@ async def _get_entity_from_string(self: 'TelegramClient', string):
|
|||
try:
|
||||
result = await self(
|
||||
_tl.fn.contacts.ResolveUsername(username))
|
||||
except errors.UsernameNotOccupiedError as e:
|
||||
except errors.USERNAME_NOT_OCCUPIED as e:
|
||||
raise ValueError('No user has "{}" as username'
|
||||
.format(username)) from e
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from datetime import datetime, timezone, timedelta
|
|||
from io import BytesIO
|
||||
from struct import unpack
|
||||
|
||||
from ..errors import TypeNotFoundError
|
||||
from ..errors._custom import TypeNotFoundError
|
||||
from .. import _tl
|
||||
from ..types import _core
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import itertools
|
|||
|
||||
from .._misc import utils
|
||||
from .. import _tl
|
||||
from ..sessions.types import Entity
|
||||
from .._sessions.types import Entity
|
||||
|
||||
# Which updates have the following fields?
|
||||
_has_field = {
|
||||
|
|
|
@ -8,7 +8,7 @@ from hashlib import sha1
|
|||
|
||||
from .. import helpers, _tl
|
||||
from .._crypto import AES, AuthKey, Factorization, rsa
|
||||
from ..errors import SecurityError
|
||||
from ..errors._custom import SecurityError
|
||||
from .._misc.binaryreader import BinaryReader
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ try:
|
|||
except ImportError:
|
||||
python_socks = None
|
||||
|
||||
from ...errors import InvalidChecksumError
|
||||
from ...errors._custom import InvalidChecksumError
|
||||
from ... import helpers
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import struct
|
|||
from zlib import crc32
|
||||
|
||||
from .connection import Connection, PacketCodec
|
||||
from ...errors import InvalidChecksumError
|
||||
from ...errors._custom import InvalidChecksumError
|
||||
|
||||
|
||||
class FullPacketCodec(PacketCodec):
|
||||
|
|
|
@ -5,7 +5,7 @@ in plain text, when no authorization key has been created yet.
|
|||
import struct
|
||||
|
||||
from .mtprotostate import MTProtoState
|
||||
from ..errors import InvalidBufferError
|
||||
from ..errors._custom import InvalidBufferError
|
||||
from .._misc.binaryreader import BinaryReader
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import struct
|
|||
|
||||
from . import authenticator
|
||||
from .._misc.messagepacker import MessagePacker
|
||||
from ..errors._rpcbase import _mk_error_type
|
||||
from .mtprotoplainsender import MTProtoPlainSender
|
||||
from .requeststate import RequestState
|
||||
from .mtprotostate import MTProtoState
|
||||
|
@ -585,12 +586,19 @@ class MTProtoSender:
|
|||
return
|
||||
|
||||
if rpc_result.error:
|
||||
error = rpc_message_to_error(rpc_result.error, state.request)
|
||||
self._send_queue.append(
|
||||
RequestState(_tl.MsgsAck([state.msg_id])))
|
||||
|
||||
if not state.future.cancelled():
|
||||
state.future.set_exception(error)
|
||||
err_ty = _mk_error_type(
|
||||
name=rpc_result.error.error_message,
|
||||
code=rpc_result.error.error_code,
|
||||
)
|
||||
state.future.set_exception(err_ty(
|
||||
rpc_result.error.error_code,
|
||||
rpc_result.error.error_message,
|
||||
state.request
|
||||
))
|
||||
else:
|
||||
try:
|
||||
with BinaryReader(rpc_result.body) as reader:
|
||||
|
|
|
@ -4,7 +4,7 @@ import time
|
|||
from hashlib import sha256
|
||||
|
||||
from .._crypto import AES
|
||||
from ..errors import SecurityError, InvalidBufferError
|
||||
from ..errors._custom import SecurityError, InvalidBufferError
|
||||
from .._misc.binaryreader import BinaryReader
|
||||
from ..types._core import TLMessage, GzipPacked
|
||||
from .._misc.tlobject import TLRequest
|
||||
|
|
|
@ -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
|
||||
)}
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
|
||||
from ... import _tl
|
||||
from ...errors import RPCError
|
||||
from ...errors._rpcbase import RpcError
|
||||
from ..._misc import markdown, tlobject
|
||||
from ..._misc.utils import get_input_peer, get_peer
|
||||
|
||||
|
@ -169,7 +169,7 @@ class Draft:
|
|||
def to_dict(self):
|
||||
try:
|
||||
entity = self.entity
|
||||
except RPCError as e:
|
||||
except RpcError as e:
|
||||
entity = e
|
||||
|
||||
return {
|
||||
|
|
|
@ -6,7 +6,7 @@ from .messagebutton import MessageButton
|
|||
from .forward import Forward
|
||||
from .file import File
|
||||
from ..._misc import utils, tlobject
|
||||
from ... import errors, _tl
|
||||
from ... import _tl
|
||||
|
||||
|
||||
def _fwd(field, doc):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name,codes,description
|
||||
2FA_CONFIRM_WAIT_X,420,The account is 2FA protected so it will be deleted in a week. Otherwise it can be reset in {seconds}
|
||||
2FA_CONFIRM_WAIT_0,420,The account is 2FA protected so it will be deleted in a week. Otherwise it can be reset in {seconds}
|
||||
ABOUT_TOO_LONG,400,The provided bio is too long
|
||||
ACCESS_TOKEN_EXPIRED,400,Bot token expired
|
||||
ACCESS_TOKEN_INVALID,400,The provided token is not valid
|
||||
|
@ -101,7 +101,7 @@ DH_G_A_INVALID,400,g_a invalid
|
|||
DOCUMENT_INVALID,400,The document file was invalid and can't be used in inline mode
|
||||
EMAIL_HASH_EXPIRED,400,The email hash expired and cannot be used to verify it
|
||||
EMAIL_INVALID,400,The given email is invalid
|
||||
EMAIL_UNCONFIRMED_X,400,"Email unconfirmed, the length of the code must be {code_length}"
|
||||
EMAIL_UNCONFIRMED_0,400,"Email unconfirmed, the length of the code must be {code_length}"
|
||||
EMOJI_INVALID,400,
|
||||
EMOJI_NOT_MODIFIED,400,
|
||||
EMOTICON_EMPTY,400,The emoticon field cannot be empty
|
||||
|
@ -124,22 +124,21 @@ FIELD_NAME_INVALID,400,The field with the name FIELD_NAME is invalid
|
|||
FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again
|
||||
FILE_CONTENT_TYPE_INVALID,400,
|
||||
FILE_ID_INVALID,400,"The provided file id is invalid. Make sure all parameters are present, have the correct type and are not empty (ID, access hash, file reference, thumb size ...)"
|
||||
FILE_MIGRATE_X,303,The file to be accessed is currently stored in DC {new_dc}
|
||||
FILE_MIGRATE_0,303,The file to be accessed is currently stored in DC {new_dc}
|
||||
FILE_PARTS_INVALID,400,The number of file parts is invalid
|
||||
FILE_PART_0_MISSING,400,File part 0 missing
|
||||
FILE_PART_EMPTY,400,The provided file part is empty
|
||||
FILE_PART_INVALID,400,The file part number is invalid
|
||||
FILE_PART_LENGTH_INVALID,400,The length of a file part is invalid
|
||||
FILE_PART_SIZE_CHANGED,400,The file part size (chunk size) cannot change during upload
|
||||
FILE_PART_SIZE_INVALID,400,The provided file part size is invalid
|
||||
FILE_PART_X_MISSING,400,Part {which} of the file is missing from storage
|
||||
FILE_PART_0_MISSING,400,Part {which} of the file is missing from storage
|
||||
FILE_REFERENCE_EMPTY,400,The file reference must exist to access the media and it cannot be empty
|
||||
FILE_REFERENCE_EXPIRED,400,The file reference has expired and is no longer valid or it belongs to self-destructing media and cannot be resent
|
||||
FILE_REFERENCE_INVALID,400,The file reference is invalid or you can't do that operation on such message
|
||||
FILE_TITLE_EMPTY,400,
|
||||
FIRSTNAME_INVALID,400,The first name is invalid
|
||||
FLOOD_TEST_PHONE_WAIT_X,420,A wait of {seconds} seconds is required in the test servers
|
||||
FLOOD_WAIT_X,420,A wait of {seconds} seconds is required
|
||||
FLOOD_TEST_PHONE_WAIT_0,420,A wait of {seconds} seconds is required in the test servers
|
||||
FLOOD_WAIT_0,420,A wait of {seconds} seconds is required
|
||||
FOLDER_ID_EMPTY,400,The folder you tried to delete was already empty
|
||||
FOLDER_ID_INVALID,400,The folder you tried to use was not valid
|
||||
FRESH_CHANGE_ADMINS_FORBIDDEN,400,Recently logged-in users cannot add or change admins
|
||||
|
@ -175,8 +174,8 @@ INPUT_LAYER_INVALID,400,The provided layer is invalid
|
|||
INPUT_METHOD_INVALID,400,The invoked method does not exist anymore or has never existed
|
||||
INPUT_REQUEST_TOO_LONG,400,The input request was too long. This may be a bug in the library as it can occur when serializing more bytes than it should (like appending the vector constructor code at the end of a message)
|
||||
INPUT_USER_DEACTIVATED,400,The specified user was deleted
|
||||
INTERDC_X_CALL_ERROR,500,An error occurred while communicating with DC {dc}
|
||||
INTERDC_X_CALL_RICH_ERROR,500,A rich error occurred while communicating with DC {dc}
|
||||
INTERDC_0_CALL_ERROR,500,An error occurred while communicating with DC {dc}
|
||||
INTERDC_0_CALL_RICH_ERROR,500,A rich error occurred while communicating with DC {dc}
|
||||
INVITE_HASH_EMPTY,400,The invite hash is empty
|
||||
INVITE_HASH_EXPIRED,400,The chat the user tried to join has expired and is not valid anymore
|
||||
INVITE_HASH_INVALID,400,The invite hash is invalid
|
||||
|
@ -218,7 +217,7 @@ MT_SEND_QUEUE_TOO_LONG,500,
|
|||
MULTI_MEDIA_TOO_LONG,400,Too many media files were included in the same album
|
||||
NEED_CHAT_INVALID,500,The provided chat is invalid
|
||||
NEED_MEMBER_INVALID,500,The provided member is invalid or does not exist (for example a thumb size)
|
||||
NETWORK_MIGRATE_X,303,The source IP address is associated with DC {new_dc}
|
||||
NETWORK_MIGRATE_0,303,The source IP address is associated with DC {new_dc}
|
||||
NEW_SALT_INVALID,400,The new salt is invalid
|
||||
NEW_SETTINGS_INVALID,400,The new settings are invalid
|
||||
NEXT_OFFSET_INVALID,400,The value for next_offset is invalid. Check that it has normal characters and is not too long
|
||||
|
@ -238,7 +237,7 @@ PASSWORD_MISSING,400,The account must have 2-factor authentication enabled (a pa
|
|||
PASSWORD_RECOVERY_EXPIRED,400,
|
||||
PASSWORD_RECOVERY_NA,400,
|
||||
PASSWORD_REQUIRED,400,The account must have 2-factor authentication enabled (a password) before this method can be used
|
||||
PASSWORD_TOO_FRESH_X,400,The password was added too recently and {seconds} seconds must pass before using the method
|
||||
PASSWORD_TOO_FRESH_0,400,The password was added too recently and {seconds} seconds must pass before using the method
|
||||
PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid
|
||||
PEER_FLOOD,400,Too many requests
|
||||
PEER_ID_INVALID,400,"An invalid Peer was used. Make sure to pass the right peer type and that the value is valid (for instance, bots cannot start conversations)"
|
||||
|
@ -250,7 +249,7 @@ PHONE_CODE_EMPTY,400,The phone code is missing
|
|||
PHONE_CODE_EXPIRED,400,The confirmation code has expired
|
||||
PHONE_CODE_HASH_EMPTY,400,The phone code hash is missing
|
||||
PHONE_CODE_INVALID,400,The phone code entered was invalid
|
||||
PHONE_MIGRATE_X,303,The phone number a user is trying to use for authorization is associated with DC {new_dc}
|
||||
PHONE_MIGRATE_0,303,The phone number a user is trying to use for authorization is associated with DC {new_dc}
|
||||
PHONE_NUMBER_APP_SIGNUP_FORBIDDEN,400,You can't sign up using this app
|
||||
PHONE_NUMBER_BANNED,400,The used phone number has been banned from Telegram and cannot be used anymore. Maybe check https://www.telegram.org/faq_spam
|
||||
PHONE_NUMBER_FLOOD,400,You asked for the code too many times.
|
||||
|
@ -276,7 +275,7 @@ POLL_OPTION_INVALID,400,A poll option used invalid data (the data may be too lon
|
|||
POLL_QUESTION_INVALID,400,The poll question was either empty or too long
|
||||
POLL_UNSUPPORTED,400,This layer does not support polls in the issued method
|
||||
POLL_VOTE_REQUIRED,403,
|
||||
PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_XMIN,406,"Similar to a flood wait, must wait {minutes} minutes"
|
||||
PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_0MIN,406,"Similar to a flood wait, must wait {minutes} minutes"
|
||||
PRIVACY_KEY_INVALID,400,The privacy key is invalid
|
||||
PRIVACY_TOO_LONG,400,Cannot add that many entities in a single request
|
||||
PRIVACY_VALUE_INVALID,400,The privacy value is invalid
|
||||
|
@ -322,16 +321,16 @@ SENSITIVE_CHANGE_FORBIDDEN,403,Your sensitive content settings cannot be changed
|
|||
SESSION_EXPIRED,401,The authorization has expired
|
||||
SESSION_PASSWORD_NEEDED,401,Two-steps verification is enabled and a password is required
|
||||
SESSION_REVOKED,401,"The authorization has been invalidated, because of the user terminating all sessions"
|
||||
SESSION_TOO_FRESH_X,400,The session logged in too recently and {seconds} seconds must pass before calling the method
|
||||
SESSION_TOO_FRESH_0,400,The session logged in too recently and {seconds} seconds must pass before calling the method
|
||||
SHA256_HASH_INVALID,400,The provided SHA256 hash is invalid
|
||||
SHORTNAME_OCCUPY_FAILED,400,An error occurred when trying to register the short-name used for the sticker pack. Try a different name
|
||||
SHORT_NAME_INVALID,400,
|
||||
SHORT_NAME_OCCUPIED,400,
|
||||
SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat
|
||||
SLOWMODE_WAIT_0,420,A wait of {seconds} seconds is required before sending another message in this chat
|
||||
SRP_ID_INVALID,400,
|
||||
START_PARAM_EMPTY,400,The start parameter is empty
|
||||
START_PARAM_INVALID,400,Start parameter invalid
|
||||
STATS_MIGRATE_X,303,The channel statistics must be fetched from DC {dc}
|
||||
STATS_MIGRATE_0,303,The channel statistics must be fetched from DC {dc}
|
||||
STICKERSET_INVALID,400,The provided sticker set is invalid
|
||||
STICKERSET_OWNER_ANONYMOUS,406,This sticker set can't be used as the group's official stickers because it was created by one of its anonymous admins
|
||||
STICKERS_EMPTY,400,No sticker provided
|
||||
|
@ -348,7 +347,7 @@ STICKER_THUMB_PNG_NOPNG,400,Stickerset thumb must be a png file but the used fil
|
|||
STICKER_THUMB_TGS_NOTGS,400,Stickerset thumb must be a tgs file but the used file was not tgs
|
||||
STORAGE_CHECK_FAILED,500,Server storage check failed
|
||||
STORE_INVALID_SCALAR_TYPE,500,
|
||||
TAKEOUT_INIT_DELAY_X,420,A wait of {seconds} seconds is required before being able to initiate the takeout
|
||||
TAKEOUT_INIT_DELAY_0,420,A wait of {seconds} seconds is required before being able to initiate the takeout
|
||||
TAKEOUT_INVALID,400,The takeout session has been invalidated by another data export session
|
||||
TAKEOUT_REQUIRED,400,You must initialize a takeout request first
|
||||
TEMP_AUTH_KEY_EMPTY,400,No temporary auth key provided
|
||||
|
@ -391,7 +390,7 @@ USER_INVALID,400,The given user was invalid
|
|||
USER_IS_BLOCKED,400 403,User is blocked
|
||||
USER_IS_BOT,400,Bots can't send messages to other bots
|
||||
USER_KICKED,400,This user was kicked from this supergroup/channel
|
||||
USER_MIGRATE_X,303,The user whose identity is being used to execute queries is associated with DC {new_dc}
|
||||
USER_MIGRATE_0,303,The user whose identity is being used to execute queries is associated with DC {new_dc}
|
||||
USER_NOT_MUTUAL_CONTACT,400 403,The provided user is not a mutual contact
|
||||
USER_NOT_PARTICIPANT,400,The target user is not a member of the specified megagroup or channel
|
||||
USER_PRIVACY_RESTRICTED,403,The user's privacy settings do not allow you to do this
|
||||
|
|
|
|
@ -7,7 +7,7 @@ account.confirmPasswordEmail,user,
|
|||
account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY
|
||||
account.createTheme,user,THEME_MIME_INVALID
|
||||
account.declinePasswordReset,user,RESET_REQUEST_MISSING
|
||||
account.deleteAccount,user,2FA_CONFIRM_WAIT_X
|
||||
account.deleteAccount,user,2FA_CONFIRM_WAIT_0
|
||||
account.deleteSecureValue,user,
|
||||
account.finishTakeoutSession,user,
|
||||
account.getAccountTTL,user,
|
||||
|
@ -57,7 +57,7 @@ account.setPrivacy,user,PRIVACY_KEY_INVALID PRIVACY_TOO_LONG
|
|||
account.unregisterDevice,user,TOKEN_INVALID
|
||||
account.updateDeviceLocked,user,
|
||||
account.updateNotifySettings,user,PEER_ID_INVALID
|
||||
account.updatePasswordSettings,user,EMAIL_UNCONFIRMED_X NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID
|
||||
account.updatePasswordSettings,user,EMAIL_UNCONFIRMED_0 NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID
|
||||
account.updateProfile,user,ABOUT_TOO_LONG FIRSTNAME_INVALID
|
||||
account.updateStatus,user,SESSION_PASSWORD_NEEDED
|
||||
account.updateTheme,user,THEME_INVALID
|
||||
|
@ -97,7 +97,7 @@ channels.deleteMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_DELETE_FORB
|
|||
channels.deleteUserHistory,user,CHANNEL_INVALID CHAT_ADMIN_REQUIRED
|
||||
channels.editAdmin,both,ADMINS_TOO_MUCH ADMIN_RANK_EMOJI_NOT_ALLOWED ADMIN_RANK_INVALID BOT_CHANNELS_NA CHANNEL_INVALID CHAT_ADMIN_INVITE_REQUIRED CHAT_ADMIN_REQUIRED FRESH_CHANGE_ADMINS_FORBIDDEN RIGHT_FORBIDDEN USER_CREATOR USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
|
||||
channels.editBanned,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID
|
||||
channels.editCreator,user,PASSWORD_MISSING PASSWORD_TOO_FRESH_X SESSION_TOO_FRESH_X SRP_ID_INVALID
|
||||
channels.editCreator,user,PASSWORD_MISSING PASSWORD_TOO_FRESH_0 SESSION_TOO_FRESH_0 SRP_ID_INVALID
|
||||
channels.editLocation,user,
|
||||
channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED FILE_REFERENCE_INVALID PHOTO_INVALID
|
||||
channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
|
||||
|
@ -251,7 +251,7 @@ messages.getWebPage,user,WC_CONVERT_URL_INVALID
|
|||
messages.getWebPagePreview,user,
|
||||
messages.hidePeerSettingsBar,user,
|
||||
messages.importChatInvite,user,CHANNELS_TOO_MUCH INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID SESSION_PASSWORD_NEEDED USERS_TOO_MUCH USER_ALREADY_PARTICIPANT
|
||||
messages.initHistoryImport,user,IMPORT_FILE_INVALID IMPORT_FORMAT_UNRECOGNIZED PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_XMIN TIMEOUT
|
||||
messages.initHistoryImport,user,IMPORT_FILE_INVALID IMPORT_FORMAT_UNRECOGNIZED PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_0MIN TIMEOUT
|
||||
messages.installStickerSet,user,STICKERSET_INVALID
|
||||
messages.markDialogUnread,user,
|
||||
messages.migrateChat,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID PEER_ID_INVALID
|
||||
|
@ -337,8 +337,8 @@ reqPq,both,
|
|||
reqPqMulti,both,
|
||||
rpcDropAnswer,both,
|
||||
setClientDHParams,both,
|
||||
stats.getBroadcastStats,user,BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED CHP_CALL_FAIL STATS_MIGRATE_X
|
||||
stats.getMegagroupStats,user,CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_X
|
||||
stats.getBroadcastStats,user,BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED CHP_CALL_FAIL STATS_MIGRATE_0
|
||||
stats.getMegagroupStats,user,CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_0
|
||||
stats.loadAsyncGraph,user,GRAPH_INVALID_RELOAD GRAPH_OUTDATED_RELOAD
|
||||
stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID STICKER_PNG_NOPNG STICKER_TGS_NOTGS
|
||||
stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID
|
||||
|
|
|
|
@ -1,60 +1,12 @@
|
|||
def generate_errors(errors, f):
|
||||
# Exact/regex match to create {CODE: ErrorClassName}
|
||||
exact_match = []
|
||||
regex_match = []
|
||||
|
||||
# Find out what subclasses to import and which to create
|
||||
import_base, create_base = set(), {}
|
||||
f.write('_captures = {\n')
|
||||
for error in errors:
|
||||
if error.subclass_exists:
|
||||
import_base.add(error.subclass)
|
||||
else:
|
||||
create_base[error.subclass] = error.int_code
|
||||
if error.capture_name:
|
||||
f.write(f" {error.canonical_name!r}: {error.capture_name!r},\n")
|
||||
f.write('}\n')
|
||||
|
||||
if error.has_captures:
|
||||
regex_match.append(error)
|
||||
else:
|
||||
exact_match.append(error)
|
||||
|
||||
# Imports and new subclass creation
|
||||
f.write('from .rpcbaseerrors import RPCError, {}\n'
|
||||
.format(", ".join(sorted(import_base))))
|
||||
|
||||
for cls, int_code in sorted(create_base.items(), key=lambda t: t[1]):
|
||||
f.write('\n\nclass {}(RPCError):\n code = {}\n'
|
||||
.format(cls, int_code))
|
||||
|
||||
# Error classes generation
|
||||
f.write('\n\n_descriptions = {\n')
|
||||
for error in errors:
|
||||
f.write('\n\nclass {}({}):\n '.format(error.name, error.subclass))
|
||||
|
||||
if error.has_captures:
|
||||
f.write('def __init__(self, request, capture=0):\n '
|
||||
' self.request = request\n ')
|
||||
f.write(' self.{} = int(capture)\n '
|
||||
.format(error.capture_name))
|
||||
else:
|
||||
f.write('def __init__(self, request):\n '
|
||||
' self.request = request\n ')
|
||||
|
||||
f.write('super(Exception, self).__init__('
|
||||
'{}'.format(repr(error.description)))
|
||||
|
||||
if error.has_captures:
|
||||
f.write('.format({0}=self.{0})'.format(error.capture_name))
|
||||
|
||||
f.write(' + self._fmt_request(self.request))\n\n')
|
||||
f.write(' def __reduce__(self):\n ')
|
||||
if error.has_captures:
|
||||
f.write('return type(self), (self.request, self.{})\n'.format(error.capture_name))
|
||||
else:
|
||||
f.write('return type(self), (self.request,)\n')
|
||||
|
||||
# Create the actual {CODE: ErrorClassName} dict once classes are defined
|
||||
f.write('\n\nrpc_errors_dict = {\n')
|
||||
for error in exact_match:
|
||||
f.write(' {}: {},\n'.format(repr(error.pattern), error.name))
|
||||
f.write('}\n\nrpc_errors_re = (\n')
|
||||
for error in regex_match:
|
||||
f.write(' ({}, {}),\n'.format(repr(error.pattern), error.name))
|
||||
f.write(')\n')
|
||||
if error.description:
|
||||
f.write(f" {error.canonical_name!r}: {error.description!r},\n")
|
||||
f.write('}\n')
|
||||
|
|
|
@ -17,25 +17,16 @@ KNOWN_BASE_CLASSES = {
|
|||
}
|
||||
|
||||
|
||||
def _get_class_name(error_code):
|
||||
def _get_canonical_name(error_code):
|
||||
"""
|
||||
Gets the corresponding class name for the given error code,
|
||||
this either being an integer (thus base error name) or str.
|
||||
Gets the corresponding canonical name for the given error code.
|
||||
"""
|
||||
if isinstance(error_code, int):
|
||||
return KNOWN_BASE_CLASSES.get(
|
||||
abs(error_code), 'RPCError' + str(error_code).replace('-', 'Neg')
|
||||
)
|
||||
# This code should match that of the library itself.
|
||||
name = re.sub(r'[-_\d]', '', error_code).lower()
|
||||
while name.endswith('error'):
|
||||
name = name[:-len('error')]
|
||||
|
||||
if error_code.startswith('2'):
|
||||
error_code = re.sub(r'2', 'TWO_', error_code, count=1)
|
||||
|
||||
if re.match(r'\d+', error_code):
|
||||
raise RuntimeError('error code starting with a digit cannot have valid Python name: {}'.format(error_code))
|
||||
|
||||
return snake_to_camel_case(
|
||||
error_code.replace('FIRSTNAME', 'FIRST_NAME')\
|
||||
.replace('SLOWMODE', 'SLOW_MODE').lower(), suffix='Error')
|
||||
return name
|
||||
|
||||
|
||||
class Error:
|
||||
|
@ -45,18 +36,13 @@ class Error:
|
|||
# Telegram isn't exactly consistent with returned errors anyway.
|
||||
self.int_code = codes[0]
|
||||
self.str_code = name
|
||||
self.subclass = _get_class_name(codes[0])
|
||||
self.subclass_exists = abs(codes[0]) in KNOWN_BASE_CLASSES
|
||||
self.canonical_name = _get_canonical_name(name)
|
||||
self.description = description
|
||||
|
||||
self.has_captures = '_X' in name
|
||||
if self.has_captures:
|
||||
self.name = _get_class_name(name.replace('_X', '_'))
|
||||
self.pattern = name.replace('_X', r'_(\d+)')
|
||||
has_captures = '0' in name
|
||||
if has_captures:
|
||||
self.capture_name = re.search(r'{(\w+)}', description).group(1)
|
||||
else:
|
||||
self.name = _get_class_name(name)
|
||||
self.pattern = name
|
||||
self.capture_name = None
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user