@@ -392,11 +392,19 @@ async def _dispatch_update(self: 'TelegramClient', update, others, channel_id, p
await self._get_difference(update, channel_id, pts_date)
except OSError:
pass # We were disconnected, that's okay
except errors.RPCError:
# There's a high chance the request fails because we lack
# the channel. Because these "happen sporadically" (#1428)
# we should be okay (no flood waits) even if more occur.
pass

if not self._self_input_peer:
# Some updates require our own ID, so we must make sure
# that the event builder has offline access to it. Calling
# `get_me()` will cache it under `self._self_input_peer`.
#
# It will return `None` if we haven't logged in yet which is
# fine, we will just retry next time anyway.
await self.get_me(input_peer=True)

built = EventBuilderDict(self, update, others)
@@ -451,6 +459,46 @@ async def _dispatch_update(self: 'TelegramClient', update, others, channel_id, p
self._log[__name__].exception('Unhandled exception on %s',
name)

async def _dispatch_event(self: 'TelegramClient', event):
"""
Dispatches a single, out-of-order event. Used by `AlbumHack`.
"""
# We're duplicating a most logic from `_dispatch_update`, but all in
# the name of speed; we don't want to make it worse for all updates
# just because albums may need it.
for builder, callback in self._event_builders:
if not isinstance(event, builder.Event):
continue

if not builder.resolved:
await builder.resolve(self)

filter = builder.filter(event)
if inspect.isawaitable(filter):
filter = await filter
if not filter:
continue

try:
await callback(event)
except errors.AlreadyInConversationError:
name = getattr(callback, '__name__', repr(callback))
self._log[__name__].debug(
'Event handler "%s" already has an open conversation, '
'ignoring new one', name)
except events.StopPropagation:
name = getattr(callback, '__name__', repr(callback))
self._log[__name__].debug(
'Event handler "%s" stopped chain of propagation '
'for event %s.', name, type(event).__name__
)
break
except Exception as e:
if not isinstance(e, asyncio.CancelledError) or self.is_connected():
name = getattr(callback, '__name__', repr(callback))
self._log[__name__].exception('Unhandled exception on %s',
name)

async def _get_difference(self: 'TelegramClient', update, channel_id, pts_date):
"""
Get the difference for this `channel_id` if any, then load entities.
@@ -566,8 +614,15 @@ def __getitem__(self, builder):
try:
return self.__dict__[builder]
except KeyError:
# Updates may arrive before login (like updateLoginToken) and we
# won't have our self ID yet (anyway only new messages need it).
self_id = (
self.client._self_input_peer.user_id
if self.client._self_input_peer
else None
)
event = self.__dict__[builder] = builder.build(
self.update, self.others, self.client._self_input_peer.user_id)
self.update, self.others, self_id)

if isinstance(event, EventCommon):
event.original_update = self.update
@@ -1,4 +1,6 @@
import asyncio
import time
import weakref

from .common import EventBuilder, EventCommon, name_inner_event
from .. import utils
@@ -14,6 +16,54 @@
_IGNORE_DICT = {}


_HACK_DELAY = 0.5


class AlbumHack:
"""
When receiving an album from a different data-center, they will come in
separate `Updates`, so we need to temporarily remember them for a while
and only after produce the event.
Of course events are not designed for this kind of wizardy, so this is
a dirty hack that gets the job done.
When cleaning up the code base we may want to figure out a better way
to do this, or just leave the album problem to the users; the update
handling code is bad enough as it is.
"""
def __init__(self, client, event):
# It's probably silly to use a weakref here because this object is
# very short-lived but might as well try to do "the right thing".
self._client = weakref.ref(client)
self._event = event # parent event
self._due = client.loop.time() + _HACK_DELAY

client.loop.create_task(self.deliver_event())

def extend(self, messages):
client = self._client()
if client: # weakref may be dead
self._event.messages.extend(messages)
self._due = client.loop.time() + _HACK_DELAY

async def deliver_event(self):
while True:
client = self._client()
if client is None:
return # weakref is dead, nothing to deliver

diff = self._due - client.loop.time()
if diff <= 0:
# We've hit our due time, deliver event. It won't respect
# sequential updates but fixing that would just worsen this.
await client._dispatch_event(self._event)
return

del client # Clear ref and sleep until our due time
await asyncio.sleep(diff)


@name_inner_event
class Album(EventBuilder):
"""
@@ -66,6 +116,7 @@ def build(cls, update, others=None, self_id=None):
return

# Check if the ignore list is too big, and if it is clean it
# TODO time could technically go backwards; time is not monotonic
now = time.time()
if len(_IGNORE_DICT) > _IGNORE_MAX_SIZE:
for i in [i for i, t in _IGNORE_DICT.items() if now - t > _IGNORE_MAX_AGE]:
@@ -84,6 +135,11 @@ def build(cls, update, others=None, self_id=None):
and u.message.grouped_id == group)
])

def filter(self, event):
# Albums with less than two messages require a few hacks to work.
if len(event.messages) > 1:
return super().filter(event)

class Event(EventCommon, SenderGetter):
"""
Represents the event of a new album.
@@ -115,6 +171,14 @@ def _set_client(self, client):
for msg in self.messages:
msg._finish_init(client, self._entities, None)

if len(self.messages) == 1:
# This will require hacks to be a proper album event
hack = client._albums.get(self.grouped_id)
if hack is None:
client._albums[self.grouped_id] = AlbumHack(client, self)
else:
hack.extend(self.messages)

@property
def grouped_id(self):
"""
@@ -1,6 +1,6 @@
from .common import EventBuilder, EventCommon, name_inner_event
from .. import utils, helpers
from ..tl import types, functions
from .. import utils
from ..tl import types


@name_inner_event
@@ -398,7 +398,7 @@ def input_users(self):
try:
self._input_users.append(utils.get_input_peer(self._entities[user_id]))
continue
except (KeyError, TypeError) as e:
except (KeyError, TypeError):
pass

# If missing, try from the entity cache
@@ -1,10 +1,9 @@
import abc
import asyncio
import itertools
import warnings

from .. import utils
from ..tl import TLObject, types, functions
from ..tl import TLObject, types
from ..tl.custom.chatgetter import ChatGetter


@@ -79,11 +79,11 @@ class Event(EventCommon, SenderGetter):
Represents the event of a new callback query.
Members:
query (:tl:`UpdateBotCallbackQuery`):
The original :tl:`UpdateBotCallbackQuery`.
query (:tl:`UpdateBotInlineQuery`):
The original :tl:`UpdateBotInlineQuery`.
Make sure to access the `text` of the query if
that's what you want instead working with this.
Make sure to access the `text` property of the query if
you want the text rather than the actual query object.
pattern_match (`obj`, optional):
The resulting object from calling the passed ``pattern``
@@ -1,4 +1,3 @@
import asyncio
import re

from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set
@@ -123,6 +123,8 @@ async def _cancel(log, **tasks):
except RuntimeError:
# Probably: RuntimeError: await wasn't used with future
#
# See: https://github.com/python/cpython/blob/12d3061c7819a73d891dcce44327410eaf0e1bc2/Lib/asyncio/futures.py#L265
#
# Happens with _asyncio.Task instances (in "Task cancelling" state)
# trying to SIGINT the program right during initial connection, on
# _recv_loop coroutine (but we're creating its task explicitly with
@@ -131,6 +133,12 @@ async def _cancel(log, **tasks):
# Since we're aware of this error there's no point in logging it.
# *May* be https://bugs.python.org/issue37172
pass
except AssertionError as e:
# In Python 3.6, the above RuntimeError is an AssertionError
# See https://github.com/python/cpython/blob/7df32f844efed33ca781a016017eab7050263b90/Lib/asyncio/futures.py#L328
if e.args != ("yield from wasn't used with future",):
log.exception('Unhandled exception from %s after cancelling '
'%s (%s)', name, type(task), task)
except Exception:
log.exception('Unhandled exception from %s after cancelling '
'%s (%s)', name, type(task), task)
@@ -162,7 +162,6 @@ def compute_check(request: types.account.Password, password: str):

def generate_and_check_random():
random_size = 256
import time
while True:
random = os.urandom(random_size)
a = int.from_bytes(random, 'big')
@@ -1,4 +1,3 @@
import datetime
import inspect

from .tl import types
@@ -1,7 +1,7 @@
import gzip
import struct

from .. import TLObject, TLRequest
from .. import TLObject


class GzipPacked(TLObject):
@@ -10,3 +10,4 @@
from .inlineresult import InlineResult
from .inlineresults import InlineResults
from .conversation import Conversation
from .qrlogin import QRLogin
@@ -132,7 +132,8 @@ def mark_read(self, message=None):

def get_response(self, message=None, *, timeout=None):
"""
Gets the next message that responds to a previous one.
Gets the next message that responds to a previous one. This is
the method you need most of the time, along with `get_edit`.
Args:
message (`Message <telethon.tl.custom.message.Message>` | `int`, optional):
@@ -142,6 +143,16 @@ def get_response(self, message=None, *, timeout=None):
timeout (`int` | `float`, optional):
If present, this `timeout` (in seconds) will override the
per-action timeout defined for the conversation.
.. code-block:: python
async with client.conversation(...) as conv:
await conv.send_message('Hey, what is your name?')
response = await conv.get_response()
name = response.text
await conv.send_message('Nice to meet you, {}!'.format(name))
"""
return self._get_message(
message, self._response_indices, self._pending_responses, timeout,
@@ -272,23 +283,41 @@ async def wait_event(self, event, *, timeout=None):
.. note::
Only use this if there isn't another method available!
**Only use this if there isn't another method available!**
For example, don't use `wait_event` for new messages,
since `get_response` already exists, etc.
Unless you're certain that your code will run fast enough,
generally you should get a "handle" of this special coroutine
before acting. Generally, you should do this:
>>> from telethon import TelegramClient, events
>>>
>>> client = TelegramClient(...)
>>>
>>> async def main():
>>> async with client.conversation(...) as conv:
>>> response = conv.wait_event(events.NewMessage(incoming=True))
>>> await conv.send_message('Hi')
>>> response = await response
before acting. In this example you will see how to wait for a user
to join a group with proper use of `wait_event`:
.. code-block:: python
from telethon import TelegramClient, events
client = TelegramClient(...)
group_id = ...
async def main():
# Could also get the user id from an event; this is just an example
user_id = ...
async with client.conversation(user_id) as conv:
# Get a handle to the future event we'll wait for
handle = conv.wait_event(events.ChatAction(
group_id,
func=lambda e: e.user_joined and e.user_id == user_id
))
# Perform whatever action in between
await conv.send_message('Please join this group before speaking to me!')
# Wait for the event we registered above to fire
event = await handle
# Continue with the conversation
await conv.send_message('Thanks!')
This way your event can be registered before acting,
since the response may arrive before your event was
@@ -764,7 +764,8 @@ async def download_media(self, *args, **kwargs):
return await self._client.download_media(self, *args, **kwargs)

async def click(self, i=None, j=None,
*, text=None, filter=None, data=None):
*, text=None, filter=None, data=None, share_phone=None,
share_geo=None):
"""
Calls `button.click <telethon.tl.custom.messagebutton.MessageButton.click>`
on the specified button.
@@ -813,6 +814,28 @@ async def click(self, i=None, j=None,
that if the message does not have this data, it will
``raise DataInvalidError``.
share_phone (`bool` | `str` | tl:`InputMediaContact`):
When clicking on a keyboard button requesting a phone number
(:tl:`KeyboardButtonRequestPhone`), this argument must be
explicitly set to avoid accidentally sharing the number.
It can be `True` to automatically share the current user's
phone, a string to share a specific phone number, or a contact
media to specify all details.
If the button is pressed without this, `ValueError` is raised.
share_geo (`tuple` | `list` | tl:`InputMediaGeoPoint`):
When clicking on a keyboard button requesting a geo location
(:tl:`KeyboardButtonRequestGeoLocation`), this argument must
be explicitly set to avoid accidentally sharing the location.
It must be a `tuple` of `float` as ``(longitude, latitude)``,
or a :tl:`InputGeoPoint` instance to avoid accidentally using
the wrong roder.
If the button is pressed without this, `ValueError` is raised.
Example:
.. code-block:: python
@@ -828,6 +851,9 @@ async def click(self, i=None, j=None,
# Click by data
await message.click(data=b'payload')
# Click on a button requesting a phone
await message.click(0, share_phone=True)
"""
if not self._client:
return
@@ -844,7 +870,7 @@ async def click(self, i=None, j=None,
data=data
)
)
except errors.BotTimeout:
except errors.BotResponseTimeoutError:
return None

if sum(int(x is not None) for x in (i, text, filter)) >= 2:
@@ -853,29 +879,35 @@ async def click(self, i=None, j=None,
if not await self.get_buttons():
return # Accessing the property sets self._buttons[_flat]

if text is not None:
if callable(text):
for button in self._buttons_flat:
if text(button.text):
return await button.click()
else:
def find_button():
nonlocal i
if text is not None:
if callable(text):
for button in self._buttons_flat:
if text(button.text):
return button
else:
for button in self._buttons_flat:
if button.text == text:
return button
return

if filter is not None:
for button in self._buttons_flat:
if button.text == text:
return await button.click()
return
if filter(button):
return button
return

if filter is not None:
for button in self._buttons_flat:
if filter(button):
return await button.click()
return
if i is None:
i = 0
if j is None:
return self._buttons_flat[i]
else:
return self._buttons[i][j]

if i is None:
i = 0
if j is None:
return await self._buttons_flat[i].click()
else:
return await self._buttons[i][j].click()
button = find_button()
if button:
return await button.click(share_phone=share_phone, share_geo=share_geo)

async def mark_read(self):
"""
@@ -1,5 +1,5 @@
from .. import types, functions
from ...errors import BotTimeout
from ...errors import BotResponseTimeoutError
import webbrowser


@@ -59,7 +59,7 @@ def url(self):
if isinstance(self.button, types.KeyboardButtonUrl):
return self.button.url

async def click(self):
async def click(self, share_phone=None, share_geo=None):
"""
Emulates the behaviour of clicking this button.
@@ -75,6 +75,19 @@ async def click(self):
If it's a :tl:`KeyboardButtonUrl`, the URL of the button will
be passed to ``webbrowser.open`` and return `True` on success.
If it's a :tl:`KeyboardButtonRequestPhone`, you must indicate that you
want to ``share_phone=True`` in order to share it. Sharing it is not a
default because it is a privacy concern and could happen accidentally.
You may also use ``share_phone=phone`` to share a specific number, in
which case either `str` or :tl:`InputMediaContact` should be used.
If it's a :tl:`KeyboardButtonRequestGeoLocation`, you must pass a
tuple in ``share_geo=(longitude, latitude)``. Note that Telegram seems
to have some heuristics to determine impossible locations, so changing
this value a lot quickly may not work as expected. You may also pass a
:tl:`InputGeoPoint` if you find the order confusing.
"""
if isinstance(self.button, types.KeyboardButton):
return await self._client.send_message(
@@ -85,7 +98,7 @@ async def click(self):
)
try:
return await self._client(req)
except BotTimeout:
except BotResponseTimeoutError:
return None
elif isinstance(self.button, types.KeyboardButtonSwitchInline):
return await self._client(functions.messages.StartBotRequest(
@@ -99,5 +112,28 @@ async def click(self):
)
try:
return await self._client(req)
except BotTimeout:
except BotResponseTimeoutError:
return None
elif isinstance(self.button, types.KeyboardButtonRequestPhone):
if not share_phone:
raise ValueError('cannot click on phone buttons unless share_phone=True')

if share_phone == True or isinstance(share_phone, str):
me = await self._client.get_me()
share_phone = types.InputMediaContact(
phone_number=me.phone if share_phone == True else share_phone,
first_name=me.first_name or '',
last_name=me.last_name or '',
vcard=''
)

return await self._client.send_file(self._chat, share_phone)
elif isinstance(self.button, types.KeyboardButtonRequestGeoLocation):
if not share_geo:
raise ValueError('cannot click on geo buttons unless share_geo=(longitude, latitude)')

if isinstance(share_geo, (tuple, list)):
long, lat = share_geo
share_geo = types.InputMediaGeoPoint(types.InputGeoPoint(lat=lat, long=long))

return await self._client.send_file(self._chat, share_geo)
@@ -0,0 +1,119 @@
import asyncio
import base64
import datetime

from .. import types, functions
from ... import events


class QRLogin:
"""
QR login information.
Most of the time, you will present the `url` as a QR code to the user,
and while it's being shown, call `wait`.
"""
def __init__(self, client, ignored_ids):
self._client = client
self._request = functions.auth.ExportLoginTokenRequest(
self._client.api_id, self._client.api_hash, ignored_ids)
self._resp = None

async def recreate(self):
"""
Generates a new token and URL for a new QR code, useful if the code
has expired before it was imported.
"""
self._resp = await self._client(self._request)

@property
def token(self) -> bytes:
"""
The binary data representing the token.
It can be used by a previously-authorized client in a call to
:tl:`auth.importLoginToken` to log the client that originally
requested the QR login.
"""
return self._resp.token

@property
def url(self) -> str:
"""
The ``tg://login`` URI with the token. When opened by a Telegram
application where the user is logged in, it will import the login
token.
If you want to display a QR code to the user, this is the URL that
should be launched when the QR code is scanned (the URL that should
be contained in the QR code image you generate).
Whether you generate the QR code image or not is up to you, and the
library can't do this for you due to the vast ways of generating and
displaying the QR code that exist.
The URL simply consists of `token` base64-encoded.
"""
return 'tg://login?token={}'.format(base64.b64encode(self._resp.token).decode('utf-8'))

@property
def expires(self) -> datetime.datetime:
"""
The `datetime` at which the QR code will expire.
If you want to try again, you will need to call `recreate`.
"""
return self._resp.expires

async def wait(self, timeout: float = None):
"""
Waits for the token to be imported by a previously-authorized client,
either by scanning the QR, launching the URL directly, or calling the
import method.
This method **must** be called before the QR code is scanned, and
must be executing while the QR code is being scanned. Otherwise, the
login will not complete.
Will raise `asyncio.TimeoutError` if the login doesn't complete on
time.
Arguments
timeout (float):
The timeout, in seconds, to wait before giving up. By default
the library will wait until the token expires, which is often
what you want.
Returns
On success, an instance of :tl:`User`. On failure it will raise.
"""
if timeout is None:
timeout = (self._resp.expires - datetime.datetime.now(tz=datetime.timezone.utc)).total_seconds()

event = asyncio.Event()

async def handler(_update):
event.set()

self._client.add_event_handler(handler, events.Raw(types.UpdateLoginToken))

try:
# Will raise timeout error if it doesn't complete quick enough,
# which we want to let propagate
await asyncio.wait_for(event.wait(), timeout=timeout)
finally:
self._client.remove_event_handler(handler)

# We got here without it raising timeout error, so we can proceed
resp = await self._client(self._request)
if isinstance(resp, types.auth.LoginTokenMigrateTo):
await self._client._switch_dc(resp.dc_id)
resp = await self._client(functions.auth.ImportLoginTokenRequest(resp.token))
# resp should now be auth.loginTokenSuccess

if isinstance(resp, types.auth.LoginTokenSuccess):
user = resp.authorization.user
self._client._on_login(user)
return user

raise TypeError('Login token response was unexpected: {}'.format(resp))
@@ -1,3 +1,3 @@
# Versions should comply with PEP440.
# This line is parsed in setup.py:
__version__ = '1.14.0'
__version__ = '1.15.0'
@@ -357,6 +357,7 @@ updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> =
updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update;
updateDialogFilterOrder#a5d72105 order:Vector<int> = Update;
updateDialogFilters#3504914f = Update;
updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update;

updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;

@@ -421,7 +422,7 @@ inputDocumentEmpty#72f0eaae = InputDocument;
inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;

documentEmpty#36f8c871 id:long = Document;
document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;
document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector<PhotoSize> video_thumbs:flags.1?Vector<VideoSize> dc_id:int attributes:Vector<DocumentAttribute> = Document;

help.support#17c6b5f6 phone_number:string user:User = help.Support;

@@ -1140,6 +1141,8 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA
help.promoDataEmpty#98f6ac75 expires:int = help.PromoData;
help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector<Chat> users:Vector<User> psa_type:flags.1?string psa_message:flags.2?string = help.PromoData;

videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize;

---functions---

invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1485,6 +1488,7 @@ phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool;
phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates;
phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;

langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
@@ -1498,4 +1502,4 @@ folders.deleteFolder#1c295881 folder_id:int = Updates;
stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;

// LAYER 113
// LAYER 114
@@ -15,16 +15,21 @@ AUTH_KEY_INVALID,401,The key is invalid
AUTH_KEY_PERM_EMPTY,401,"The method is unavailable for temporary authorization key, not bound to permanent"
AUTH_KEY_UNREGISTERED,401,The key is not registered in the system
AUTH_RESTART,500,Restart the authorization process
AUTH_TOKEN_ALREADY_ACCEPTED,400,The authorization token was already used
AUTH_TOKEN_EXPIRED,400,The provided authorization token has expired and the updated QR-code must be re-scanned
AUTH_TOKEN_INVALID,400,An invalid authorization token was provided
BANNED_RIGHTS_INVALID,400,"You cannot use that set of permissions in this request, i.e. restricting view_messages as a default"
BOTS_TOO_MUCH,400,There are too many bots in this chat/channel
BOT_CHANNELS_NA,400,Bots can't edit admin privileges
BOT_COMMAND_DESCRIPTION_INVALID,400,"The command description was empty, too long or had invalid characters used"
BOT_GROUPS_BLOCKED,400,This bot can't be added to groups
BOT_INLINE_DISABLED,400,This bot can't be used in inline mode
BOT_INVALID,400,This is not a valid bot
BOT_METHOD_INVALID,400,The API access for bot users is restricted. The method you tried to invoke cannot be executed as a bot
BOT_MISSING,400,This method can only be run by a bot
BOT_PAYMENTS_DISABLED,400,This method can only be run by a bot
BOT_POLLS_DISABLED,400,You cannot create polls under a bot account
BOT_RESPONSE_TIMEOUT,400,The bot did not answer to the callback query in time
BROADCAST_ID_INVALID,400,The channel is invalid
BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public
BUTTON_DATA_INVALID,400,The provided button data is invalid
@@ -67,6 +72,7 @@ CONNECTION_LANG_PACK_INVALID,400,"The specified language pack is not valid. This
CONNECTION_LAYER_INVALID,400,The very first request must always be InvokeWithLayerRequest
CONNECTION_NOT_INITED,400,Connection not initialized
CONNECTION_SYSTEM_EMPTY,400,Connection system empty
CONNECTION_SYSTEM_LANG_CODE_EMPTY,400,The system language string was empty during connection
CONTACT_ID_INVALID,400,The provided contact ID is invalid
CONTACT_NAME_EMPTY,400,The provided contact name cannot be empty
DATA_INVALID,400,Encrypted data invalid
@@ -103,6 +109,7 @@ FILE_PART_SIZE_CHANGED,400,The file part size (chunk size) cannot change during
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_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
FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again
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
@@ -158,6 +165,7 @@ MESSAGE_ID_INVALID,400,"The specified message ID is invalid or you can't do that
MESSAGE_NOT_MODIFIED,400,Content of the message was not modified
MESSAGE_POLL_CLOSED,400,The poll was closed and can no longer be voted on
MESSAGE_TOO_LONG,400,Message was too long. Current maximum length is 4096 UTF-8 characters
METHOD_INVALID,400,The API method is invalid and cannot be used
MSGID_DECREASE_RETRY,500,The request should be retried with a lower message ID
MSG_ID_INVALID,400,The message ID used in the peer was invalid
MSG_WAIT_FAILED,400,A waiting call returned an error
@@ -178,7 +186,9 @@ PARTICIPANT_CALL_FAILED,500,Failure while making call
PARTICIPANT_VERSION_OUTDATED,400,The other participant does not use an up to date telegram client with support for calls
PASSWORD_EMPTY,400,The provided password is empty
PASSWORD_HASH_INVALID,400,The password (and thus its hash value) you entered is invalid
PASSWORD_MISSING,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
PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid
PEER_FLOOD,,Too many requests
PEER_ID_INVALID,400,An invalid Peer was used. Make sure to pass the right peer type
@@ -248,6 +258,7 @@ SEND_MESSAGE_TYPE_INVALID,400,The message type is invalid
SESSION_EXPIRED,401,The authorization has expired
SESSION_PASSWORD_NEEDED,401,Two-steps verification is enabled and a password is required
SESSION_REVOKED,401,"The authorization has been invalidated, because of the user terminating all sessions"
SESSION_TOO_FRESH_X,400,The session logged in too recently and {seconds} seconds must pass before calling the method
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
SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat
@@ -72,7 +72,7 @@ auth.exportAuthorization,both,DC_ID_INVALID
auth.exportLoginToken,user,
auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID
auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID
auth.importLoginToken,user,
auth.importLoginToken,user,AUTH_TOKEN_ALREADY_ACCEPTED AUTH_TOKEN_EXPIRED AUTH_TOKEN_INVALID
auth.logOut,both,
auth.recoverPassword,user,CODE_EMPTY
auth.requestPasswordRecovery,user,PASSWORD_EMPTY
@@ -83,7 +83,7 @@ auth.signIn,user,PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NU
auth.signUp,user,FIRSTNAME_INVALID MEMBER_OCCUPY_PRIMARY_LOC_FAILED PHONE_CODE_EMPTY PHONE_CODE_EXPIRED PHONE_CODE_INVALID PHONE_NUMBER_FLOOD PHONE_NUMBER_INVALID PHONE_NUMBER_OCCUPIED REG_ID_GENERATE_FAILED
bots.answerWebhookJSONQuery,bot,QUERY_ID_INVALID USER_BOT_INVALID
bots.sendCustomRequest,bot,USER_BOT_INVALID
bots.setBotCommands,bot,
bots.setBotCommands,bot,BOT_COMMAND_DESCRIPTION_INVALID
channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID
channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED
channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE
@@ -92,7 +92,7 @@ channels.deleteMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_DELETE_FORB
channels.deleteUserHistory,user,CHANNEL_INVALID CHAT_ADMIN_REQUIRED
channels.editAdmin,both,ADMINS_TOO_MUCH ADMIN_RANK_EMOJI_NOT_ALLOWED ADMIN_RANK_INVALID BOT_CHANNELS_NA CHANNEL_INVALID CHAT_ADMIN_INVITE_REQUIRED CHAT_ADMIN_REQUIRED FRESH_CHANGE_ADMINS_FORBIDDEN RIGHT_FORBIDDEN USER_CREATOR USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
channels.editBanned,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID
channels.editCreator,user,
channels.editCreator,user,PASSWORD_MISSING PASSWORD_TOO_FRESH_X SESSION_TOO_FRESH_X
channels.editLocation,user,
channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID
channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
@@ -199,7 +199,7 @@ messages.getAllDrafts,user,
messages.getAllStickers,user,
messages.getArchivedStickers,user,
messages.getAttachedStickers,user,
messages.getBotCallbackAnswer,user,CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout
messages.getBotCallbackAnswer,user,BOT_RESPONSE_TIMEOUT CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout
messages.getChats,both,CHAT_ID_INVALID PEER_ID_INVALID
messages.getCommonChats,user,USER_ID_INVALID
messages.getDhConfig,user,RANDOM_LENGTH_INVALID
@@ -265,14 +265,14 @@ messages.saveDraft,user,PEER_ID_INVALID
messages.saveGif,user,GIF_ID_INVALID
messages.saveRecentSticker,user,STICKER_ID_INVALID
messages.search,user,CHAT_ADMIN_REQUIRED INPUT_CONSTRUCTOR_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID PEER_ID_NOT_SUPPORTED SEARCH_QUERY_EMPTY USER_ID_INVALID
messages.searchGifs,user,SEARCH_QUERY_EMPTY
messages.searchGifs,user,METHOD_INVALID SEARCH_QUERY_EMPTY
messages.searchGlobal,user,SEARCH_QUERY_EMPTY
messages.searchStickerSets,user,
messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED
messages.sendEncryptedFile,user,MSG_WAIT_FAILED
messages.sendEncryptedService,user,DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED
messages.sendInlineBotResult,user,CHAT_SEND_INLINE_FORBIDDEN CHAT_WRITE_FORBIDDEN INLINE_RESULT_EXPIRED PEER_ID_INVALID QUERY_ID_EMPTY SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY GAME_BOT_INVALID INPUT_USER_DEACTIVATED MEDIA_CAPTION_TOO_LONG MEDIA_EMPTY PAYMENT_PROVIDER_INVALID PEER_ID_INVALID PHOTO_EXT_INVALID PHOTO_INVALID_DIMENSIONS PHOTO_SAVE_FILE_INVALID POLL_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID RANDOM_ID_DUPLICATE SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH STORAGE_CHECK_FAILED Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT VIDEO_CONTENT_TYPE_INVALID WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
messages.sendMedia,both,BOT_PAYMENTS_DISABLED BOT_POLLS_DISABLED BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_SEND_MEDIA_FORBIDDEN CHAT_WRITE_FORBIDDEN EMOTICON_INVALID EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID FILE_REFERENCE_EMPTY FILE_REFERENCE_EXPIRED GAME_BOT_INVALID INPUT_USER_DEACTIVATED MEDIA_CAPTION_TOO_LONG MEDIA_EMPTY PAYMENT_PROVIDER_INVALID PEER_ID_INVALID PHOTO_EXT_INVALID PHOTO_INVALID_DIMENSIONS PHOTO_SAVE_FILE_INVALID POLL_ANSWERS_INVALID POLL_OPTION_DUPLICATE POLL_QUESTION_INVALID QUIZ_CORRECT_ANSWERS_EMPTY QUIZ_CORRECT_ANSWERS_TOO_MUCH QUIZ_CORRECT_ANSWER_INVALID QUIZ_MULTIPLE_INVALID RANDOM_ID_DUPLICATE SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH STORAGE_CHECK_FAILED Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT VIDEO_CONTENT_TYPE_INVALID WEBPAGE_CURL_FAILED WEBPAGE_MEDIA_EMPTY
messages.sendMessage,both,AUTH_KEY_DUPLICATED BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_RESTRICTED CHAT_WRITE_FORBIDDEN ENTITIES_TOO_LONG ENTITY_MENTION_USER_INVALID INPUT_USER_DEACTIVATED MESSAGE_EMPTY MESSAGE_TOO_LONG MSG_ID_INVALID PEER_ID_INVALID POLL_OPTION_INVALID RANDOM_ID_DUPLICATE REPLY_MARKUP_INVALID REPLY_MARKUP_TOO_LONG SCHEDULE_BOT_NOT_ALLOWED SCHEDULE_DATE_TOO_LATE SCHEDULE_STATUS_PRIVATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
messages.sendMultiMedia,both,SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH
messages.sendScheduledMessages,user,