mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-25 10:53:44 +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/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
|
||||||
|
|
|
@ -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
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
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
|
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 {
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
|
@ -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,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')
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user