Completely overhaul errors to be generated dynamically

This commit is contained in:
Lonami Exo 2021-09-24 20:07:34 +02:00
parent cfe47a0434
commit debde6e856
26 changed files with 345 additions and 318 deletions

2
.gitignore vendored
View File

@ -2,7 +2,7 @@
/telethon/_tl/fn/ /telethon/_tl/fn/
/telethon/_tl/*.py /telethon/_tl/*.py
/telethon/_tl/alltlobjects.py /telethon/_tl/alltlobjects.py
/telethon/errors/rpcerrorlist.py /telethon/errors/_generated.py
# User session # User session
*.session *.session

View File

@ -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. 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 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`` 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. 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 Using a flat list to define buttons will now create rows and not columns
------------------------------------------------------------------------ ------------------------------------------------------------------------

View File

@ -47,7 +47,7 @@ GENERATOR_DIR = Path('telethon_generator')
LIBRARY_DIR = Path('telethon') LIBRARY_DIR = Path('telethon')
ERRORS_IN = GENERATOR_DIR / 'data/errors.csv' 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' METHODS_IN = GENERATOR_DIR / 'data/methods.csv'

View File

@ -676,7 +676,7 @@ async def get_permissions(
for participant in chat.full_chat.participants.participants: for participant in chat.full_chat.participants.participants:
if participant.user_id == user.user_id: if participant.user_id == user.user_id:
return _custom.ParticipantPermissions(participant, True) 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') raise ValueError('You must pass either a channel or a chat')
@ -694,7 +694,7 @@ async def get_stats(
try: try:
req = _tl.fn.stats.GetMessageStats(entity, message) req = _tl.fn.stats.GetMessageStats(entity, message)
return await self(req) return await self(req)
except errors.StatsMigrateError as e: except errors.STATS_MIGRATE as e:
dc = e.dc dc = e.dc
else: else:
# Don't bother fetching the Channel entity (costs a request), instead # Don't bother fetching the Channel entity (costs a request), instead
@ -703,13 +703,13 @@ async def get_stats(
try: try:
req = _tl.fn.stats.GetBroadcastStats(entity) req = _tl.fn.stats.GetBroadcastStats(entity)
return await self(req) return await self(req)
except errors.StatsMigrateError as e: except errors.STATS_MIGRATE as e:
dc = e.dc dc = e.dc
except errors.BroadcastRequiredError: except errors.BROADCAST_REQUIRED:
req = _tl.fn.stats.GetMegagroupStats(entity) req = _tl.fn.stats.GetMegagroupStats(entity)
try: try:
return await self(req) return await self(req)
except errors.StatsMigrateError as e: except errors.STATS_MIGRATE as e:
dc = e.dc dc = e.dc
sender = await self._borrow_exported_sender(dc) sender = await self._borrow_exported_sender(dc)

View File

@ -232,7 +232,7 @@ async def delete_dialog(
result = await self(_tl.fn.messages.DeleteChatUser( result = await self(_tl.fn.messages.DeleteChatUser(
entity.chat_id, _tl.InputUserSelf(), revoke_history=revoke 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 # Happens if we didn't have the deactivated information
result = None result = None
else: else:

View File

@ -12,8 +12,8 @@ from .. import version, helpers, __name__ as __base_name__, _tl
from .._crypto import rsa from .._crypto import rsa
from .._misc import markdown, entitycache, statecache, enums from .._misc import markdown, entitycache, statecache, enums
from .._network import MTProtoSender, Connection, ConnectionTcpFull, connection as conns from .._network import MTProtoSender, Connection, ConnectionTcpFull, connection as conns
from ..sessions import Session, SQLiteSession, MemorySession from .._sessions import Session, SQLiteSession, MemorySession
from ..sessions.types import DataCenter, SessionState from .._sessions.types import DataCenter, SessionState
DEFAULT_DC_ID = 2 DEFAULT_DC_ID = 2
DEFAULT_IPV4_IP = '149.154.167.51' DEFAULT_IPV4_IP = '149.154.167.51'

View File

@ -8,7 +8,8 @@ import traceback
import typing import typing
import logging import logging
from .. import events, utils, errors, _tl from .. import events, utils, _tl
from ..errors._rpcbase import RpcError
from ..events.common import EventBuilder, EventCommon from ..events.common import EventBuilder, EventCommon
if typing.TYPE_CHECKING: 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) await _get_difference(self, update, channel_id, pts_date)
except OSError: except OSError:
pass # We were disconnected, that's okay pass # We were disconnected, that's okay
except errors.RPCError: except RpcError:
# There's a high chance the request fails because we lack # There's a high chance the request fails because we lack
# the channel. Because these "happen sporadically" (#1428) # the channel. Because these "happen sporadically" (#1428)
# we should be okay (no flood waits) even if more occur. # 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() await self.catch_up()
self._log[__name__].info('Successfully fetched missed updates') 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 ' self._log[__name__].warning('Failed to get missed updates after '
'reconnect: %r', e) 'reconnect: %r', e)
except Exception: except Exception:

View File

@ -4,10 +4,11 @@ import itertools
import time import time
import typing import typing
from ..errors._custom import MultiError
from ..errors._rpcbase import RpcError, ServerError, FloodError, InvalidDcError, UnauthorizedError
from .. import errors, hints, _tl from .. import errors, hints, _tl
from .._misc import helpers, utils 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!') _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) await asyncio.sleep(diff)
self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None) self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None)
else: else:
raise errors.FloodWaitError(request=r, capture=diff) raise errors.FLOOD_WAIT(420, f'FLOOD_WAIT_{diff}', request=r)
if self._no_updates: if self._no_updates:
r = _tl.fn.InvokeWithoutUpdates(r) r = _tl.fn.InvokeWithoutUpdates(r)
@ -67,7 +68,7 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
for f in future: for f in future:
try: try:
result = await f result = await f
except RPCError as e: except RpcError as e:
exceptions.append(e) exceptions.append(e)
results.append(None) results.append(None)
continue continue
@ -87,22 +88,20 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
if entities: if entities:
await self.session.insert_entities(entities) await self.session.insert_entities(entities)
return result return result
except (errors.ServerError, errors.RpcCallFailError, except ServerError as e:
errors.RpcMcgetFailError, errors.InterdcCallErrorError,
errors.InterdcCallRichErrorError) as e:
last_error = e last_error = e
self._log[__name__].warning( self._log[__name__].warning(
'Telegram is having internal issues %s: %s', 'Telegram is having internal issues %s: %s',
e.__class__.__name__, e) e.__class__.__name__, e)
await asyncio.sleep(2) await asyncio.sleep(2)
except (errors.FloodWaitError, errors.SlowModeWaitError, errors.FloodTestPhoneWaitError) as e: except FloodError as e:
last_error = e last_error = e
if utils.is_list_like(request): if utils.is_list_like(request):
request = request[request_index] request = request[request_index]
# SLOW_MODE_WAIT is chat-specific, not request-specific # SLOWMODE_WAIT is chat-specific, not request-specific
if not isinstance(e, errors.SlowModeWaitError): if not isinstance(e, errors.SLOWMODE_WAIT):
self._flood_waited_requests\ self._flood_waited_requests\
[request.CONSTRUCTOR_ID] = time.time() + e.seconds [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) await asyncio.sleep(e.seconds)
else: else:
raise raise
except (errors.PhoneMigrateError, errors.NetworkMigrateError, except InvalidDcError as e:
errors.UserMigrateError) as e:
last_error = e last_error = e
self._log[__name__].info('Phone migrated to %d', e.new_dc) self._log[__name__].info('Phone migrated to %d', e.new_dc)
should_raise = isinstance(e, ( should_raise = isinstance(e, (
errors.PhoneMigrateError, errors.NetworkMigrateError errors.PHONE_MIGRATE, errors.NETWORK_MIGRATE
)) ))
if should_raise and await self.is_user_authorized(): if should_raise and await self.is_user_authorized():
raise raise
@ -138,7 +136,7 @@ async def get_me(self: 'TelegramClient', input_peer: bool = False) \
try: try:
me = (await self(_tl.fn.users.GetUsers([_tl.InputUserSelf()])))[0] me = (await self(_tl.fn.users.GetUsers([_tl.InputUserSelf()])))[0]
return utils.get_input_peer(me, allow_self=False) if input_peer else me return utils.get_input_peer(me, allow_self=False) if input_peer else me
except errors.UnauthorizedError: except UnauthorizedError:
return None return None
async def is_bot(self: 'TelegramClient') -> bool: 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 # Any request that requires authorization will work
await self(_tl.fn.updates.GetState()) await self(_tl.fn.updates.GetState())
self._authorized = True self._authorized = True
except errors.RPCError: except RpcError:
self._authorized = False self._authorized = False
return self._authorized return self._authorized
@ -290,7 +288,7 @@ async def get_input_entity(
channels = await self(_tl.fn.channels.GetChannels([ channels = await self(_tl.fn.channels.GetChannels([
_tl.InputChannel(peer.channel_id, access_hash=0)])) _tl.InputChannel(peer.channel_id, access_hash=0)]))
return utils.get_input_peer(channels.chats[0]) return utils.get_input_peer(channels.chats[0])
except errors.ChannelInvalidError: except errors.CHANNEL_INVALID:
pass pass
raise ValueError( raise ValueError(
@ -338,7 +336,7 @@ async def _get_entity_from_string(self: 'TelegramClient', string):
_tl.fn.contacts.GetContacts(0))).users: _tl.fn.contacts.GetContacts(0))).users:
if user.phone == phone: if user.phone == phone:
return user return user
except errors.BotMethodInvalidError: except errors.BOT_METHOD_INVALID:
raise ValueError('Cannot get entity by phone number as a ' raise ValueError('Cannot get entity by phone number as a '
'bot (try using integer IDs, not strings)') 'bot (try using integer IDs, not strings)')
elif string.lower() in ('me', 'self'): elif string.lower() in ('me', 'self'):
@ -360,7 +358,7 @@ async def _get_entity_from_string(self: 'TelegramClient', string):
try: try:
result = await self( result = await self(
_tl.fn.contacts.ResolveUsername(username)) _tl.fn.contacts.ResolveUsername(username))
except errors.UsernameNotOccupiedError as e: except errors.USERNAME_NOT_OCCUPIED as e:
raise ValueError('No user has "{}" as username' raise ValueError('No user has "{}" as username'
.format(username)) from e .format(username)) from e

View File

@ -7,7 +7,7 @@ from datetime import datetime, timezone, timedelta
from io import BytesIO from io import BytesIO
from struct import unpack from struct import unpack
from ..errors import TypeNotFoundError from ..errors._custom import TypeNotFoundError
from .. import _tl from .. import _tl
from ..types import _core from ..types import _core

View File

@ -3,7 +3,7 @@ import itertools
from .._misc import utils from .._misc import utils
from .. import _tl from .. import _tl
from ..sessions.types import Entity from .._sessions.types import Entity
# Which updates have the following fields? # Which updates have the following fields?
_has_field = { _has_field = {

View File

@ -8,7 +8,7 @@ from hashlib import sha1
from .. import helpers, _tl from .. import helpers, _tl
from .._crypto import AES, AuthKey, Factorization, rsa from .._crypto import AES, AuthKey, Factorization, rsa
from ..errors import SecurityError from ..errors._custom import SecurityError
from .._misc.binaryreader import BinaryReader from .._misc.binaryreader import BinaryReader

View File

@ -13,7 +13,7 @@ try:
except ImportError: except ImportError:
python_socks = None python_socks = None
from ...errors import InvalidChecksumError from ...errors._custom import InvalidChecksumError
from ... import helpers from ... import helpers

View File

@ -2,7 +2,7 @@ import struct
from zlib import crc32 from zlib import crc32
from .connection import Connection, PacketCodec from .connection import Connection, PacketCodec
from ...errors import InvalidChecksumError from ...errors._custom import InvalidChecksumError
class FullPacketCodec(PacketCodec): class FullPacketCodec(PacketCodec):

View File

@ -5,7 +5,7 @@ in plain text, when no authorization key has been created yet.
import struct import struct
from .mtprotostate import MTProtoState from .mtprotostate import MTProtoState
from ..errors import InvalidBufferError from ..errors._custom import InvalidBufferError
from .._misc.binaryreader import BinaryReader from .._misc.binaryreader import BinaryReader

View File

@ -4,6 +4,7 @@ import struct
from . import authenticator from . import authenticator
from .._misc.messagepacker import MessagePacker from .._misc.messagepacker import MessagePacker
from ..errors._rpcbase import _mk_error_type
from .mtprotoplainsender import MTProtoPlainSender from .mtprotoplainsender import MTProtoPlainSender
from .requeststate import RequestState from .requeststate import RequestState
from .mtprotostate import MTProtoState from .mtprotostate import MTProtoState
@ -585,12 +586,19 @@ class MTProtoSender:
return return
if rpc_result.error: if rpc_result.error:
error = rpc_message_to_error(rpc_result.error, state.request)
self._send_queue.append( self._send_queue.append(
RequestState(_tl.MsgsAck([state.msg_id]))) RequestState(_tl.MsgsAck([state.msg_id])))
if not state.future.cancelled(): 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: else:
try: try:
with BinaryReader(rpc_result.body) as reader: with BinaryReader(rpc_result.body) as reader:

View File

@ -4,7 +4,7 @@ import time
from hashlib import sha256 from hashlib import sha256
from .._crypto import AES from .._crypto import AES
from ..errors import SecurityError, InvalidBufferError from ..errors._custom import SecurityError, InvalidBufferError
from .._misc.binaryreader import BinaryReader from .._misc.binaryreader import BinaryReader
from ..types._core import TLMessage, GzipPacked from ..types._core import TLMessage, GzipPacked
from .._misc.tlobject import TLRequest from .._misc.tlobject import TLRequest

View File

@ -1,46 +1,48 @@
""" import sys
This module holds all the base and automatically generated errors that the
Telegram API has. See telethon_generator/errors.json for more.
"""
import re
from .common import ( from ._custom import (
ReadCancelledError, TypeNotFoundError, InvalidChecksumError, ReadCancelledError,
InvalidBufferError, SecurityError, CdnFileTamperedError, TypeNotFoundError,
BadMessageError, MultiError 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 if sys.version_info < (3, 7):
from .rpcbaseerrors import * # https://stackoverflow.com/a/7668273/
from .rpcerrorlist import * 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): sys.modules[__name__] = _TelethonErrors(_mk_error_type, globals())
""" else:
Converts a Telegram's RPC Error to a Python error. # https://www.python.org/dev/peps/pep-0562/
def __getattr__(name):
return _mk_error_type(name=name)
:param rpc_error: the RpcError instance. del sys
: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)

144
telethon/errors/_rpcbase.py Normal file
View 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.
""")

View File

@ -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
)}

View File

@ -1,7 +1,7 @@
import datetime import datetime
from ... import _tl from ... import _tl
from ...errors import RPCError from ...errors._rpcbase import RpcError
from ..._misc import markdown, tlobject from ..._misc import markdown, tlobject
from ..._misc.utils import get_input_peer, get_peer from ..._misc.utils import get_input_peer, get_peer
@ -169,7 +169,7 @@ class Draft:
def to_dict(self): def to_dict(self):
try: try:
entity = self.entity entity = self.entity
except RPCError as e: except RpcError as e:
entity = e entity = e
return { return {

View File

@ -6,7 +6,7 @@ from .messagebutton import MessageButton
from .forward import Forward from .forward import Forward
from .file import File from .file import File
from ..._misc import utils, tlobject from ..._misc import utils, tlobject
from ... import errors, _tl from ... import _tl
def _fwd(field, doc): def _fwd(field, doc):

View File

@ -1,5 +1,5 @@
name,codes,description 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 ABOUT_TOO_LONG,400,The provided bio is too long
ACCESS_TOKEN_EXPIRED,400,Bot token expired ACCESS_TOKEN_EXPIRED,400,Bot token expired
ACCESS_TOKEN_INVALID,400,The provided token is not valid 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 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_HASH_EXPIRED,400,The email hash expired and cannot be used to verify it
EMAIL_INVALID,400,The given email is invalid 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_INVALID,400,
EMOJI_NOT_MODIFIED,400, EMOJI_NOT_MODIFIED,400,
EMOTICON_EMPTY,400,The emoticon field cannot be empty 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 FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again
FILE_CONTENT_TYPE_INVALID,400, 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_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_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_EMPTY,400,The provided file part is empty
FILE_PART_INVALID,400,The file part number is invalid 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_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_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_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_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_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_REFERENCE_INVALID,400,The file reference is invalid or you can't do that operation on such message
FILE_TITLE_EMPTY,400, FILE_TITLE_EMPTY,400,
FIRSTNAME_INVALID,400,The first name is invalid 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_TEST_PHONE_WAIT_0,420,A wait of {seconds} seconds is required in the test servers
FLOOD_WAIT_X,420,A wait of {seconds} seconds is required 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_EMPTY,400,The folder you tried to delete was already empty
FOLDER_ID_INVALID,400,The folder you tried to use was not valid 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 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_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_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 INPUT_USER_DEACTIVATED,400,The specified user was deleted
INTERDC_X_CALL_ERROR,500,An error occurred while communicating with DC {dc} INTERDC_0_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_RICH_ERROR,500,A rich error occurred while communicating with DC {dc}
INVITE_HASH_EMPTY,400,The invite hash is empty 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_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 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 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_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) 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_SALT_INVALID,400,The new salt is invalid
NEW_SETTINGS_INVALID,400,The new settings are 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 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_EXPIRED,400,
PASSWORD_RECOVERY_NA,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_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 PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid
PEER_FLOOD,400,Too many requests 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)" 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_EXPIRED,400,The confirmation code has expired
PHONE_CODE_HASH_EMPTY,400,The phone code hash is missing PHONE_CODE_HASH_EMPTY,400,The phone code hash is missing
PHONE_CODE_INVALID,400,The phone code entered was invalid 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_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_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. 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_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_UNSUPPORTED,400,This layer does not support polls in the issued method
POLL_VOTE_REQUIRED,403, 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_KEY_INVALID,400,The privacy key is invalid
PRIVACY_TOO_LONG,400,Cannot add that many entities in a single request PRIVACY_TOO_LONG,400,Cannot add that many entities in a single request
PRIVACY_VALUE_INVALID,400,The privacy value is invalid 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_EXPIRED,401,The authorization has expired
SESSION_PASSWORD_NEEDED,401,Two-steps verification is enabled and a password is required 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_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 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 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_INVALID,400,
SHORT_NAME_OCCUPIED,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, SRP_ID_INVALID,400,
START_PARAM_EMPTY,400,The start parameter is empty START_PARAM_EMPTY,400,The start parameter is empty
START_PARAM_INVALID,400,Start parameter invalid 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_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 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 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 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 STORAGE_CHECK_FAILED,500,Server storage check failed
STORE_INVALID_SCALAR_TYPE,500, 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_INVALID,400,The takeout session has been invalidated by another data export session
TAKEOUT_REQUIRED,400,You must initialize a takeout request first TAKEOUT_REQUIRED,400,You must initialize a takeout request first
TEMP_AUTH_KEY_EMPTY,400,No temporary auth key provided 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_BLOCKED,400 403,User is blocked
USER_IS_BOT,400,Bots can't send messages to other bots USER_IS_BOT,400,Bots can't send messages to other bots
USER_KICKED,400,This user was kicked from this supergroup/channel 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_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_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 USER_PRIVACY_RESTRICTED,403,The user's privacy settings do not allow you to do this

1 name codes description
2 2FA_CONFIRM_WAIT_X 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}
3 ABOUT_TOO_LONG 400 The provided bio is too long
4 ACCESS_TOKEN_EXPIRED 400 Bot token expired
5 ACCESS_TOKEN_INVALID 400 The provided token is not valid
101 DOCUMENT_INVALID 400 The document file was invalid and can't be used in inline mode
102 EMAIL_HASH_EXPIRED 400 The email hash expired and cannot be used to verify it
103 EMAIL_INVALID 400 The given email is invalid
104 EMAIL_UNCONFIRMED_X EMAIL_UNCONFIRMED_0 400 Email unconfirmed, the length of the code must be {code_length}
105 EMOJI_INVALID 400
106 EMOJI_NOT_MODIFIED 400
107 EMOTICON_EMPTY 400 The emoticon field cannot be empty
124 FILEREF_UPGRADE_NEEDED 406 The file reference needs to be refreshed before being used again
125 FILE_CONTENT_TYPE_INVALID 400
126 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 ...)
127 FILE_MIGRATE_X FILE_MIGRATE_0 303 The file to be accessed is currently stored in DC {new_dc}
128 FILE_PARTS_INVALID 400 The number of file parts is invalid
FILE_PART_0_MISSING 400 File part 0 missing
129 FILE_PART_EMPTY 400 The provided file part is empty
130 FILE_PART_INVALID 400 The file part number is invalid
131 FILE_PART_LENGTH_INVALID 400 The length of a file part is invalid
132 FILE_PART_SIZE_CHANGED 400 The file part size (chunk size) cannot change during upload
133 FILE_PART_SIZE_INVALID 400 The provided file part size is invalid
134 FILE_PART_X_MISSING FILE_PART_0_MISSING 400 Part {which} of the file is missing from storage
135 FILE_REFERENCE_EMPTY 400 The file reference must exist to access the media and it cannot be empty
136 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
137 FILE_REFERENCE_INVALID 400 The file reference is invalid or you can't do that operation on such message
138 FILE_TITLE_EMPTY 400
139 FIRSTNAME_INVALID 400 The first name is invalid
140 FLOOD_TEST_PHONE_WAIT_X FLOOD_TEST_PHONE_WAIT_0 420 A wait of {seconds} seconds is required in the test servers
141 FLOOD_WAIT_X FLOOD_WAIT_0 420 A wait of {seconds} seconds is required
142 FOLDER_ID_EMPTY 400 The folder you tried to delete was already empty
143 FOLDER_ID_INVALID 400 The folder you tried to use was not valid
144 FRESH_CHANGE_ADMINS_FORBIDDEN 400 Recently logged-in users cannot add or change admins
174 INPUT_METHOD_INVALID 400 The invoked method does not exist anymore or has never existed
175 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)
176 INPUT_USER_DEACTIVATED 400 The specified user was deleted
177 INTERDC_X_CALL_ERROR INTERDC_0_CALL_ERROR 500 An error occurred while communicating with DC {dc}
178 INTERDC_X_CALL_RICH_ERROR INTERDC_0_CALL_RICH_ERROR 500 A rich error occurred while communicating with DC {dc}
179 INVITE_HASH_EMPTY 400 The invite hash is empty
180 INVITE_HASH_EXPIRED 400 The chat the user tried to join has expired and is not valid anymore
181 INVITE_HASH_INVALID 400 The invite hash is invalid
217 MULTI_MEDIA_TOO_LONG 400 Too many media files were included in the same album
218 NEED_CHAT_INVALID 500 The provided chat is invalid
219 NEED_MEMBER_INVALID 500 The provided member is invalid or does not exist (for example a thumb size)
220 NETWORK_MIGRATE_X NETWORK_MIGRATE_0 303 The source IP address is associated with DC {new_dc}
221 NEW_SALT_INVALID 400 The new salt is invalid
222 NEW_SETTINGS_INVALID 400 The new settings are invalid
223 NEXT_OFFSET_INVALID 400 The value for next_offset is invalid. Check that it has normal characters and is not too long
237 PASSWORD_RECOVERY_EXPIRED 400
238 PASSWORD_RECOVERY_NA 400
239 PASSWORD_REQUIRED 400 The account must have 2-factor authentication enabled (a password) before this method can be used
240 PASSWORD_TOO_FRESH_X PASSWORD_TOO_FRESH_0 400 The password was added too recently and {seconds} seconds must pass before using the method
241 PAYMENT_PROVIDER_INVALID 400 The payment provider was not recognised or its token was invalid
242 PEER_FLOOD 400 Too many requests
243 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)
249 PHONE_CODE_EXPIRED 400 The confirmation code has expired
250 PHONE_CODE_HASH_EMPTY 400 The phone code hash is missing
251 PHONE_CODE_INVALID 400 The phone code entered was invalid
252 PHONE_MIGRATE_X PHONE_MIGRATE_0 303 The phone number a user is trying to use for authorization is associated with DC {new_dc}
253 PHONE_NUMBER_APP_SIGNUP_FORBIDDEN 400 You can't sign up using this app
254 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
255 PHONE_NUMBER_FLOOD 400 You asked for the code too many times.
275 POLL_QUESTION_INVALID 400 The poll question was either empty or too long
276 POLL_UNSUPPORTED 400 This layer does not support polls in the issued method
277 POLL_VOTE_REQUIRED 403
278 PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_XMIN PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_0MIN 406 Similar to a flood wait, must wait {minutes} minutes
279 PRIVACY_KEY_INVALID 400 The privacy key is invalid
280 PRIVACY_TOO_LONG 400 Cannot add that many entities in a single request
281 PRIVACY_VALUE_INVALID 400 The privacy value is invalid
321 SESSION_EXPIRED 401 The authorization has expired
322 SESSION_PASSWORD_NEEDED 401 Two-steps verification is enabled and a password is required
323 SESSION_REVOKED 401 The authorization has been invalidated, because of the user terminating all sessions
324 SESSION_TOO_FRESH_X SESSION_TOO_FRESH_0 400 The session logged in too recently and {seconds} seconds must pass before calling the method
325 SHA256_HASH_INVALID 400 The provided SHA256 hash is invalid
326 SHORTNAME_OCCUPY_FAILED 400 An error occurred when trying to register the short-name used for the sticker pack. Try a different name
327 SHORT_NAME_INVALID 400
328 SHORT_NAME_OCCUPIED 400
329 SLOWMODE_WAIT_X SLOWMODE_WAIT_0 420 A wait of {seconds} seconds is required before sending another message in this chat
330 SRP_ID_INVALID 400
331 START_PARAM_EMPTY 400 The start parameter is empty
332 START_PARAM_INVALID 400 Start parameter invalid
333 STATS_MIGRATE_X STATS_MIGRATE_0 303 The channel statistics must be fetched from DC {dc}
334 STICKERSET_INVALID 400 The provided sticker set is invalid
335 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
336 STICKERS_EMPTY 400 No sticker provided
347 STICKER_THUMB_TGS_NOTGS 400 Stickerset thumb must be a tgs file but the used file was not tgs
348 STORAGE_CHECK_FAILED 500 Server storage check failed
349 STORE_INVALID_SCALAR_TYPE 500
350 TAKEOUT_INIT_DELAY_X TAKEOUT_INIT_DELAY_0 420 A wait of {seconds} seconds is required before being able to initiate the takeout
351 TAKEOUT_INVALID 400 The takeout session has been invalidated by another data export session
352 TAKEOUT_REQUIRED 400 You must initialize a takeout request first
353 TEMP_AUTH_KEY_EMPTY 400 No temporary auth key provided
390 USER_IS_BLOCKED 400 403 User is blocked
391 USER_IS_BOT 400 Bots can't send messages to other bots
392 USER_KICKED 400 This user was kicked from this supergroup/channel
393 USER_MIGRATE_X USER_MIGRATE_0 303 The user whose identity is being used to execute queries is associated with DC {new_dc}
394 USER_NOT_MUTUAL_CONTACT 400 403 The provided user is not a mutual contact
395 USER_NOT_PARTICIPANT 400 The target user is not a member of the specified megagroup or channel
396 USER_PRIVACY_RESTRICTED 403 The user's privacy settings do not allow you to do this

View File

@ -7,7 +7,7 @@ account.confirmPasswordEmail,user,
account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY
account.createTheme,user,THEME_MIME_INVALID account.createTheme,user,THEME_MIME_INVALID
account.declinePasswordReset,user,RESET_REQUEST_MISSING account.declinePasswordReset,user,RESET_REQUEST_MISSING
account.deleteAccount,user,2FA_CONFIRM_WAIT_X account.deleteAccount,user,2FA_CONFIRM_WAIT_0
account.deleteSecureValue,user, account.deleteSecureValue,user,
account.finishTakeoutSession,user, account.finishTakeoutSession,user,
account.getAccountTTL,user, account.getAccountTTL,user,
@ -57,7 +57,7 @@ account.setPrivacy,user,PRIVACY_KEY_INVALID PRIVACY_TOO_LONG
account.unregisterDevice,user,TOKEN_INVALID account.unregisterDevice,user,TOKEN_INVALID
account.updateDeviceLocked,user, account.updateDeviceLocked,user,
account.updateNotifySettings,user,PEER_ID_INVALID 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.updateProfile,user,ABOUT_TOO_LONG FIRSTNAME_INVALID
account.updateStatus,user,SESSION_PASSWORD_NEEDED account.updateStatus,user,SESSION_PASSWORD_NEEDED
account.updateTheme,user,THEME_INVALID 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.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.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.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.editLocation,user,
channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED FILE_REFERENCE_INVALID PHOTO_INVALID channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED FILE_REFERENCE_INVALID PHOTO_INVALID
channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED 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.getWebPagePreview,user,
messages.hidePeerSettingsBar,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.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.installStickerSet,user,STICKERSET_INVALID
messages.markDialogUnread,user, messages.markDialogUnread,user,
messages.migrateChat,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID PEER_ID_INVALID messages.migrateChat,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID PEER_ID_INVALID
@ -337,8 +337,8 @@ reqPq,both,
reqPqMulti,both, reqPqMulti,both,
rpcDropAnswer,both, rpcDropAnswer,both,
setClientDHParams,both, setClientDHParams,both,
stats.getBroadcastStats,user,BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED CHP_CALL_FAIL 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_X stats.getMegagroupStats,user,CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_0
stats.loadAsyncGraph,user,GRAPH_INVALID_RELOAD GRAPH_OUTDATED_RELOAD stats.loadAsyncGraph,user,GRAPH_INVALID_RELOAD GRAPH_OUTDATED_RELOAD
stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID STICKER_PNG_NOPNG STICKER_TGS_NOTGS stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID STICKER_PNG_NOPNG STICKER_TGS_NOTGS
stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID

1 method usability errors
7 account.confirmPhone user CODE_HASH_INVALID PHONE_CODE_EMPTY
8 account.createTheme user THEME_MIME_INVALID
9 account.declinePasswordReset user RESET_REQUEST_MISSING
10 account.deleteAccount user 2FA_CONFIRM_WAIT_X 2FA_CONFIRM_WAIT_0
11 account.deleteSecureValue user
12 account.finishTakeoutSession user
13 account.getAccountTTL user
57 account.unregisterDevice user TOKEN_INVALID
58 account.updateDeviceLocked user
59 account.updateNotifySettings user PEER_ID_INVALID
60 account.updatePasswordSettings user EMAIL_UNCONFIRMED_X NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID EMAIL_UNCONFIRMED_0 NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID
61 account.updateProfile user ABOUT_TOO_LONG FIRSTNAME_INVALID
62 account.updateStatus user SESSION_PASSWORD_NEEDED
63 account.updateTheme user THEME_INVALID
97 channels.deleteUserHistory user CHANNEL_INVALID CHAT_ADMIN_REQUIRED
98 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
99 channels.editBanned both CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID
100 channels.editCreator user PASSWORD_MISSING PASSWORD_TOO_FRESH_X SESSION_TOO_FRESH_X SRP_ID_INVALID PASSWORD_MISSING PASSWORD_TOO_FRESH_0 SESSION_TOO_FRESH_0 SRP_ID_INVALID
101 channels.editLocation user
102 channels.editPhoto both CHANNEL_INVALID CHAT_ADMIN_REQUIRED FILE_REFERENCE_INVALID PHOTO_INVALID
103 channels.editTitle both CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
251 messages.getWebPagePreview user
252 messages.hidePeerSettingsBar user
253 messages.importChatInvite user CHANNELS_TOO_MUCH INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID SESSION_PASSWORD_NEEDED USERS_TOO_MUCH USER_ALREADY_PARTICIPANT
254 messages.initHistoryImport user IMPORT_FILE_INVALID IMPORT_FORMAT_UNRECOGNIZED PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_XMIN TIMEOUT IMPORT_FILE_INVALID IMPORT_FORMAT_UNRECOGNIZED PREVIOUS_CHAT_IMPORT_ACTIVE_WAIT_0MIN TIMEOUT
255 messages.installStickerSet user STICKERSET_INVALID
256 messages.markDialogUnread user
257 messages.migrateChat user CHAT_ADMIN_REQUIRED CHAT_ID_INVALID PEER_ID_INVALID
337 reqPqMulti both
338 rpcDropAnswer both
339 setClientDHParams both
340 stats.getBroadcastStats user BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED CHP_CALL_FAIL STATS_MIGRATE_X BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED CHP_CALL_FAIL STATS_MIGRATE_0
341 stats.getMegagroupStats user CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_X CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_0
342 stats.loadAsyncGraph user GRAPH_INVALID_RELOAD GRAPH_OUTDATED_RELOAD
343 stickers.addStickerToSet bot BOT_MISSING STICKERSET_INVALID STICKER_PNG_NOPNG STICKER_TGS_NOTGS
344 stickers.changeStickerPosition bot BOT_MISSING STICKER_INVALID

View File

@ -1,60 +1,12 @@
def generate_errors(errors, f): def generate_errors(errors, f):
# Exact/regex match to create {CODE: ErrorClassName} f.write('_captures = {\n')
exact_match = []
regex_match = []
# Find out what subclasses to import and which to create
import_base, create_base = set(), {}
for error in errors: for error in errors:
if error.subclass_exists: if error.capture_name:
import_base.add(error.subclass) f.write(f" {error.canonical_name!r}: {error.capture_name!r},\n")
else: f.write('}\n')
create_base[error.subclass] = error.int_code
if error.has_captures: f.write('\n\n_descriptions = {\n')
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
for error in errors: for error in errors:
f.write('\n\nclass {}({}):\n '.format(error.name, error.subclass)) if error.description:
f.write(f" {error.canonical_name!r}: {error.description!r},\n")
if error.has_captures: f.write('}\n')
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')

View File

@ -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, Gets the corresponding canonical name for the given error code.
this either being an integer (thus base error name) or str.
""" """
if isinstance(error_code, int): # This code should match that of the library itself.
return KNOWN_BASE_CLASSES.get( name = re.sub(r'[-_\d]', '', error_code).lower()
abs(error_code), 'RPCError' + str(error_code).replace('-', 'Neg') while name.endswith('error'):
) name = name[:-len('error')]
if error_code.startswith('2'): return name
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')
class Error: class Error:
@ -45,18 +36,13 @@ class Error:
# Telegram isn't exactly consistent with returned errors anyway. # Telegram isn't exactly consistent with returned errors anyway.
self.int_code = codes[0] self.int_code = codes[0]
self.str_code = name self.str_code = name
self.subclass = _get_class_name(codes[0]) self.canonical_name = _get_canonical_name(name)
self.subclass_exists = abs(codes[0]) in KNOWN_BASE_CLASSES
self.description = description self.description = description
self.has_captures = '_X' in name has_captures = '0' in name
if self.has_captures: if has_captures:
self.name = _get_class_name(name.replace('_X', '_'))
self.pattern = name.replace('_X', r'_(\d+)')
self.capture_name = re.search(r'{(\w+)}', description).group(1) self.capture_name = re.search(r'{(\w+)}', description).group(1)
else: else:
self.name = _get_class_name(name)
self.pattern = name
self.capture_name = None self.capture_name = None