Sync upstream

This commit is contained in:
Pascal Jürgens 2020-08-23 12:50:06 +02:00
commit 51842d4fda
63 changed files with 1822 additions and 490 deletions

View File

@ -41,7 +41,7 @@ use these if possible.
# ...to your contacts # ...to your contacts
await client.send_message('+34600123123', 'Hello, friend!') await client.send_message('+34600123123', 'Hello, friend!')
# ...or even to any username # ...or even to any username
await client.send_message('TelethonChat', 'Hello, Telethon!') await client.send_message('username', 'Testing Telethon!')
# You can, of course, use markdown in your messages: # You can, of course, use markdown in your messages:
message = await client.send_message( message = await client.send_message(

View File

@ -99,7 +99,7 @@ You will still need an API ID and hash, but the process is very similar:
api_id = 12345 api_id = 12345
api_hash = '0123456789abcdef0123456789abcdef' api_hash = '0123456789abcdef0123456789abcdef'
bot_token = '12345:0123456789abcdef0123456789abcdef bot_token = '12345:0123456789abcdef0123456789abcdef'
# We have to manually call "start" if we want an explicit bot token # We have to manually call "start" if we want an explicit bot token
bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token) bot = TelegramClient('bot', api_id, api_hash).start(bot_token=bot_token)

View File

@ -219,19 +219,21 @@ Can I use threads?
================== ==================
Yes, you can, but you must understand that the loops themselves are Yes, you can, but you must understand that the loops themselves are
not thread safe. and you must be sure to know what is happening. You not thread safe. and you must be sure to know what is happening. The
may want to create a loop in a new thread and make sure to pass it to easiest and cleanest option is to use `asyncio.run` to create and manage
the client: the new event loop for you:
.. code-block:: python .. code-block:: python
import asyncio import asyncio
import threading import threading
def go(): async def actual_work():
loop = asyncio.new_event_loop()
client = TelegramClient(..., loop=loop) client = TelegramClient(..., loop=loop)
... ... # can use `await` here
def go():
asyncio.run(actual_work())
threading.Thread(target=go).start() threading.Thread(target=go).start()
@ -308,27 +310,26 @@ you can run requests in parallel:
async def main(): async def main():
last, sent, download_path = await asyncio.gather( last, sent, download_path = await asyncio.gather(
client.get_messages('TelethonChat', 10), client.get_messages('telegram', 10),
client.send_message('TelethonOfftopic', 'Hey guys!'), client.send_message('me', 'Using asyncio!'),
client.download_profile_photo('TelethonChat') client.download_profile_photo('telegram')
) )
loop.run_until_complete(main()) loop.run_until_complete(main())
This code will get the 10 last messages from `@TelethonChat This code will get the 10 last messages from `@telegram
<https://t.me/TelethonChat>`_, send one to `@TelethonOfftopic <https://t.me/telegram>`_, send one to the chat with yourself, and also
<https://t.me/TelethonOfftopic>`_, and also download the profile download the profile photo of the channel. `asyncio` will run all these
photo of the main group. `asyncio` will run all these three tasks three tasks at the same time. You can run all the tasks you want this way.
at the same time. You can run all the tasks you want this way.
A different way would be: A different way would be:
.. code-block:: python .. code-block:: python
loop.create_task(client.get_messages('TelethonChat', 10)) loop.create_task(client.get_messages('telegram', 10))
loop.create_task(client.send_message('TelethonOfftopic', 'Hey guys!')) loop.create_task(client.send_message('me', 'Using asyncio!'))
loop.create_task(client.download_profile_photo('TelethonChat')) loop.create_task(client.download_profile_photo('telegram'))
They will run in the background as long as the loop is running too. They will run in the background as long as the loop is running too.

View File

@ -178,7 +178,7 @@ exist, which just have the ID. You cannot get the hash out of them since
you should not be needing it. The library probably has cached it before. you should not be needing it. The library probably has cached it before.
Peers are enough to identify an entity, but they are not enough to make Peers are enough to identify an entity, but they are not enough to make
a request with them use them. You need to know their hash before you can a request with them. You need to know their hash before you can
"use them", and to know the hash you need to "encounter" them, let it "use them", and to know the hash you need to "encounter" them, let it
be in your dialogs, participants, message forwards, etc. be in your dialogs, participants, message forwards, etc.
@ -296,10 +296,10 @@ applications"? Now do the same with the library. Use what applies:
await client.get_dialogs() await client.get_dialogs()
# Are they participant of some group? Get them. # Are they participant of some group? Get them.
await client.get_participants('TelethonChat') await client.get_participants('username')
# Is the entity the original sender of a forwarded message? Get it. # Is the entity the original sender of a forwarded message? Get it.
await client.get_messages('TelethonChat', 100) await client.get_messages('username', 100)
# NOW you can use the ID, anywhere! # NOW you can use the ID, anywhere!
await client.send_message(123456, 'Hi!') await client.send_message(123456, 'Hi!')

View File

@ -100,6 +100,9 @@ There are other community-maintained implementations available:
* `Redis <https://github.com/ezdev128/telethon-session-redis>`_: * `Redis <https://github.com/ezdev128/telethon-session-redis>`_:
stores all sessions in a single Redis data store. stores all sessions in a single Redis data store.
* `MongoDB <https://github.com/watzon/telethon-session-mongo>`_:
stores the current session in a MongoDB database.
Creating your Own Storage Creating your Own Storage
========================= =========================

View File

@ -10,7 +10,7 @@ understand the official Telegram documentation) on several languages
(even more Python too), listed below: (even more Python too), listed below:
C C
* =
Possibly the most well-known unofficial open source implementation out Possibly the most well-known unofficial open source implementation out
there by `@vysheng <https://github.com/vysheng>`__, there by `@vysheng <https://github.com/vysheng>`__,
@ -29,10 +29,10 @@ published `here <https://core.telegram.org/tdlib/docs/>`__.
JavaScript JavaScript
========== ==========
`@zerobias <https://github.com/zerobias>`__ is working on `Ali Gasymov <https://github.com/alik0211>`__ made the `@mtproto/core <https://github.com/alik0211/mtproto-core>`__ library for the browser and nodejs installable via `npm <https://www.npmjs.com/package/@mtproto/core>`__.
`telegram-mtproto <https://github.com/zerobias/telegram-mtproto>`__,
a work-in-progress JavaScript library installable via `painor <https://github.com/painor>`__ is the primary author of `gramjs <https://github.com/gram-js/gramjs>`__,
`npm <https://www.npmjs.com/>`__. a Telegram client implementation in JavaScript.
Kotlin Kotlin
====== ======
@ -45,6 +45,11 @@ languages for
`@badoualy <https://github.com/badoualy>`__, currently as a beta `@badoualy <https://github.com/badoualy>`__, currently as a beta
yet working. yet working.
Language-Agnostic
=================
`Taas <https://www.t-a-a-s.ru/>`__ is a service that lets you use Telegram API with any HTTP client via API. Using tdlib under the hood, Taas is commercial service, but allows free access if you use under 5000 requests per month.
PHP PHP
=== ===

View File

@ -1,98 +0,0 @@
.. _telethon_projects:
=======================
Projects using Telethon
=======================
This page lists some **interesting and useful** real world
examples showcasing what can be built with the library.
.. note::
Do you have an interesting project that uses the library or know of any
that's not listed here? Feel free to leave a comment at
`issue 744 <https://github.com/LonamiWebs/Telethon/issues/744>`_
so it can be included in the next revision of the documentation!
You can also advertise your bot and its features, in the issue, although
it should be a big project which can be useful for others before being
included here, so please don't feel offended if it can't be here!
.. _projects-telegram-export:
telethon_examples/
==================
`telethon_examples <https://github.com/LonamiWebs/Telethon/tree/master/telethon_examples>`_ /
`Lonami's site <https://lonami.dev>`_
This documentation is not the only place where you can find useful code
snippets using the library. The main repository also has a folder with
some cool examples (even a Tkinter GUI!) which you can download, edit
and run to learn and play with them.
@TelethonSnippets
=================
`@TelethonSnippets <https://t.me/TelethonSnippets>`_
You can find useful short snippets for Telethon here.
telegram-export
===============
`telegram-export <https://github.com/expectocode/telegram-export>`_ /
`expectocode's GitHub <https://github.com/expectocode>`_
A tool to download Telegram data (users, chats, messages, and media)
into a database (and display the saved data).
.. _projects-mautrix-telegram:
mautrix-telegram
================
`mautrix-telegram <https://github.com/tulir/mautrix-telegram>`_ /
`maunium's site <https://maunium.net/>`_
A Matrix-Telegram hybrid puppeting/relaybot bridge.
.. _projects-telegramtui:
TelegramTUI
===========
`TelegramTUI <https://github.com/bad-day/TelegramTUI>`_ /
`bad-day's GitHub <https://github.com/bad-day>`_
A Telegram client on your terminal.
tgcloud
=======
`tgcloud <https://github.com/SlavikMIPT/tgcloud>`_ /
`tgcloud's site <https://dev.tgcloud.xyz/>`_
Opensource Telegram based cloud storage.
tgmount
=======
`tgmount <https://github.com/nktknshn/tgmount>`_ /
`nktknshn's GitHub <https://github.com/nktknshn>`_
Mount Telegram dialogs and channels as a Virtual File System.
garnet
======
`garnet <https://github.com/uwinx/pomegranate>`_ /
`uwinx's GitHub <https://github.com/uwinx>`_
Pomegranate (or ``garnet`` for short) is a small telethon add-on which
features persistent conversations based on Finite State Machines (FSM),
a new ``Filter`` to define handlers more conveniently and utilities to
run code on start and finish of the client. Be sure to check the project
to learn about its latest features, since this description may be out of
date.

View File

@ -13,4 +13,5 @@ Full API **will** break between different minor versions of the library,
since Telegram changes very often. The friendly methods will be kept since Telegram changes very often. The friendly methods will be kept
compatible between major versions. compatible between major versions.
If you need to see real-world examples, please refer to :ref:`telethon_projects`. If you need to see real-world examples, please refer to the
`wiki page of projects using Telethon <https://github.com/LonamiWebs/Telethon/wiki/Projects-using-Telethon>`__.

View File

@ -83,7 +83,6 @@ You can also use the menu on the left to quickly skip over sections.
examples/chats-and-channels examples/chats-and-channels
examples/users examples/users
examples/working-with-messages examples/working-with-messages
examples/projects-using-telethon
.. toctree:: .. toctree::
:hidden: :hidden:

View File

@ -13,6 +13,198 @@ it can take advantage of new goodies!
.. contents:: List of All Versions .. contents:: List of All Versions
Bug Fixes (v1.16.1)
===================
The last release added support to ``force_file`` on any media, including
things that were not possible before like ``.webp`` files. However, the
``force_document`` toggle commonly used for photos was applied "twice"
(one told the library to send it as a document, and then to send that
document as file), which prevented Telegram for analyzing the images. Long
story short, sending files to the stickers bot stopped working, but that's
been fixed now, and sending photos as documents include the size attribute
again as long as Telegram adds it.
Enhancements
~~~~~~~~~~~~
* When trying to `client.start() <telethon.client.auth.AuthMethods.start>` to
another account if you were previously logged in, the library will now warn
you because this is probably not intended. To avoid the warning, make sure
you're logging in to the right account or logout from the other first.
* Sending a copy of messages with polls will now work when possible.
* The library now automatically retries on inter-dc call errors (which occur
when Telegram has internal issues).
Bug Fixes
~~~~~~~~~
* The aforementioned issue with ``force_document``.
* Square brackets removed from IPv6 addresses. This may fix IPv6 support.
Channel Statistics (v1.16)
==========================
+------------------------+
| Scheme layer used: 116 |
+------------------------+
The newest Telegram update has a new method to also retrieve megagroup
statistics, which can now be used with `client.get_stats()
<telethon.client.chats.ChatMethods.get_stats>`. This way you'll be able
to access the raw data about your channel or megagroup statistics.
The maximum file size limit has also been increased to 2GB on the server,
so you can send even larger files.
Breaking Changes
~~~~~~~~~~~~~~~~
* Besides the obvious layer change, the ``loop`` argument **is now ignored**.
It has been deprecated since Python 3.8 and will be removed in Python 3.10,
and also caused some annoying warning messages when using certain parts of
the library. If you were (incorrectly) relying on using a different loop
from the one that was set, things may break.
Enhancements
~~~~~~~~~~~~
* `client.upload_file() <telethon.client.uploads.UploadMethods.upload_file>`
now works better when streaming files (anything that has a ``.read()``),
instead of reading it all into memory when possible.
QR login (v1.15)
================
*Published at 2020/07/04*
+------------------------+
| Scheme layer used: 114 |
+------------------------+
The library now has a friendly method to perform QR-login, as detailed in
https://core.telegram.org/api/qr-login. It won't generate QR images, but it
provides a way for you to easily do so with any other library of your choice.
Additions
~~~~~~~~~
* New `client.qr_login() <telethon.client.auth.AuthMethods.qr_login>`.
* `message.click <telethon.tl.custom.message.Message.click>` now lets you
click on buttons requesting phone or location.
Enhancements
~~~~~~~~~~~~
* Updated documentation and list of known errors.
* `events.Album <telethon.events.album.Album>` should now handle albums from
different data centers more gracefully.
* `client.download_file()
<telethon.client.downloads.DownloadMethods.download_file>` now supports
`pathlib.Path` as the destination.
Bug fixes
~~~~~~~~~
* No longer crash on updates received prior to logging in.
* Server-side changes caused clicking on inline buttons to trigger a different
error, which is now handled correctly.
Minor quality of life improvements (v1.14)
==========================================
*Published at 2020/05/26*
+------------------------+
| Scheme layer used: 113 |
+------------------------+
Some nice things that were missing, along with the usual bug-fixes.
Additions
~~~~~~~~~
* New `Message.dice <telethon.tl.custom.message.Message.dice>` property.
* The ``func=`` parameter of events can now be an ``async`` function.
Bug fixes
~~~~~~~~~
* Fixed `client.action() <telethon.client.chats.ChatMethods.action>`
having an alias wrong.
* Fixed incorrect formatting of some errors.
* Probably more reliable detection of pin events in small groups.
* Fixed send methods on `client.conversation()
<telethon.client.dialogs.DialogMethods.conversation>` were not honoring
cancellation.
* Flood waits of zero seconds are handled better.
* Getting the pinned message in a chat was failing.
* Fixed the return value when forwarding messages if some were missing
and also the return value of albums.
Enhancements
~~~~~~~~~~~~
* ``.tgs`` files are now recognised as animated stickers.
* The service message produced by `Message.pin()
<telethon.tl.custom.message.Message.pin>` is now returned.
* Sending a file with `client.send_file()
<telethon.client.uploads.UploadMethods.send_file>` now works fine when
you pass an existing dice media (e.g. sending a message copy).
* `client.edit_permissions() <telethon.client.chats.ChatMethods.edit_permissions>`
now has the ``embed_links`` parameter which was missing.
Bug Fixes (v1.13)
=================
*Published at 2020/04/25*
+------------------------+
| Scheme layer used: 112 |
+------------------------+
Bug fixes and layer bump.
Bug fixes
~~~~~~~~~
* Passing ``None`` as the entity to `client.delete_messages()
<telethon.client.messages.MessageMethods.delete_messages>` would fail.
* When downloading a thumbnail, the name inferred was wrong.
Bug Fixes (v1.12)
=================
*Published at 2020/04/20*
+------------------------+
| Scheme layer used: 111 |
+------------------------+
Once again nothing major, but a few bug fixes and primarily the new layer
deserves a new minor release.
Bug fixes
~~~~~~~~~
These were already included in the ``v1.11.3`` patch:
* ``libssl`` check was failing on macOS.
* Getting input users would sometimes fail on `events.ChatAction
<telethon.events.chataction.ChatAction>`.
These bug fixes are available in this release and beyond:
* Avoid another occurrence of `MemoryError`.
* Sending large files in albums would fail because it tried to cache them.
* The ``thumb`` was being ignored when sending files from :tl:`InputFile`.
* Fixed editing inline messages from callback queries in some cases.
* Proxy connection is now blocking which should help avoid some errors.
Bug Fixes (v1.11) Bug Fixes (v1.11)
================= =================

View File

@ -136,6 +136,15 @@ MessageButton
:show-inheritance: :show-inheritance:
QRLogin
=======
.. automodule:: telethon.qrlogin
:members:
:undoc-members:
:show-inheritance:
SenderGetter SenderGetter
============ ============

View File

@ -31,6 +31,7 @@ Auth
start start
send_code_request send_code_request
sign_in sign_in
qr_login
sign_up sign_up
log_out log_out
edit_2fa edit_2fa
@ -138,6 +139,7 @@ Chats
get_profile_photos get_profile_photos
edit_admin edit_admin
edit_permissions edit_permissions
get_stats
action action
Parse Mode Parse Mode

View File

@ -3,9 +3,10 @@ import inspect
import os import os
import sys import sys
import typing import typing
import warnings
from .. import utils, helpers, errors, password as pwd_mod from .. import utils, helpers, errors, password as pwd_mod
from ..tl import types, functions from ..tl import types, functions, custom
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .telegramclient import TelegramClient from .telegramclient import TelegramClient
@ -138,7 +139,29 @@ class AuthMethods:
if not self.is_connected(): if not self.is_connected():
await self.connect() await self.connect()
if await self.is_user_authorized(): # Rather than using `is_user_authorized`, use `get_me`. While this is
# more expensive and needs to retrieve more data from the server, it
# enables the library to warn users trying to login to a different
# account. See #1172.
me = await self.get_me()
if me is not None:
# The warnings here are on a best-effort and may fail.
if bot_token:
# bot_token's first part has the bot ID, but it may be invalid
# so don't try to parse as int (instead cast our ID to string).
if bot_token[:bot_token.find(':')] != str(me.id):
warnings.warn(
'the session already had an authorized user so it did '
'not login to the bot account using the provided '
'bot_token (it may not be using the user you expect)'
)
elif not callable(phone) and phone != me.phone:
warnings.warn(
'the session already had an authorized user so it did '
'not login to the user account using the provided '
'phone (it may not be using the user you expect)'
)
return self return self
if not bot_token: if not bot_token:
@ -496,6 +519,43 @@ class AuthMethods:
return result return result
async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> custom.QRLogin:
"""
Initiates the QR login procedure.
Note that you must be connected before invoking this, as with any
other request.
It is up to the caller to decide how to present the code to the user,
whether it's the URL, using the token bytes directly, or generating
a QR code and displaying it by other means.
See the documentation for `QRLogin` to see how to proceed after this.
Arguments
ignored_ids (List[`int`]):
List of already logged-in user IDs, to prevent logging in
twice with the same user.
Returns
An instance of `QRLogin`.
Example
.. code-block:: python
def display_url_as_qr(url):
pass # do whatever to show url as a qr to the user
qr_login = await client.qr_login()
display_url_as_qr(qr_login.url)
# Important! You need to wait for the login to complete!
await qr_login.wait()
"""
qr_login = custom.QRLogin(self, ignored_ids or [])
await qr_login.recreate()
return qr_login
async def log_out(self: 'TelegramClient') -> bool: async def log_out(self: 'TelegramClient') -> bool:
""" """
Logs out Telegram and deletes the current ``*.session`` file. Logs out Telegram and deletes the current ``*.session`` file.

View File

@ -4,7 +4,7 @@ import itertools
import string import string
import typing import typing
from .. import helpers, utils, hints from .. import helpers, utils, hints, errors
from ..requestiter import RequestIter from ..requestiter import RequestIter
from ..tl import types, functions, custom from ..tl import types, functions, custom
@ -30,13 +30,13 @@ class _ChatAction:
'audio': types.SendMessageUploadAudioAction(1), 'audio': types.SendMessageUploadAudioAction(1),
'voice': types.SendMessageUploadAudioAction(1), # alias 'voice': types.SendMessageUploadAudioAction(1), # alias
'song': types.SendMessageUploadAudioAction(1), # alias
'round': types.SendMessageUploadRoundAction(1), 'round': types.SendMessageUploadRoundAction(1),
'video': types.SendMessageUploadVideoAction(1), 'video': types.SendMessageUploadVideoAction(1),
'photo': types.SendMessageUploadPhotoAction(1), 'photo': types.SendMessageUploadPhotoAction(1),
'document': types.SendMessageUploadDocumentAction(1), 'document': types.SendMessageUploadDocumentAction(1),
'file': types.SendMessageUploadDocumentAction(1), # alias 'file': types.SendMessageUploadDocumentAction(1), # alias
'song': types.SendMessageUploadDocumentAction(1), # alias
'cancel': types.SendMessageCancelAction() 'cancel': types.SendMessageCancelAction()
} }
@ -337,9 +337,25 @@ class _ProfilePhotoIter(RequestIter):
else: else:
self.request.offset += len(result.photos) self.request.offset += len(result.photos)
else: else:
self.total = getattr(result, 'count', None)
if self.total == 0 and isinstance(result, types.messages.ChannelMessages):
# There are some broadcast channels that have a photo but this
# request doesn't retrieve it for some reason. Work around this
# issue by fetching the channel.
#
# We can't use the normal entity because that gives `ChatPhoto`
# but we want a proper `Photo`, so fetch full channel instead.
channel = await self.client(functions.channels.GetFullChannelRequest(self.request.peer))
photo = channel.full_chat.chat_photo
if isinstance(photo, types.Photo):
self.buffer = [photo]
self.total = 1
self.left = len(self.buffer)
return
self.buffer = [x.action.photo for x in result.messages self.buffer = [x.action.photo for x in result.messages
if isinstance(x.action, types.MessageActionChatEditPhoto)] if isinstance(x.action, types.MessageActionChatEditPhoto)]
self.total = getattr(result, 'count', None)
if len(result.messages) < self.request.limit: if len(result.messages) < self.request.limit:
self.left = len(self.buffer) self.left = len(self.buffer)
elif result.messages: elif result.messages:
@ -922,6 +938,7 @@ class ChatMethods:
send_gifs: bool = True, send_gifs: bool = True,
send_games: bool = True, send_games: bool = True,
send_inline: bool = True, send_inline: bool = True,
embed_link_previews: bool = True,
send_polls: bool = True, send_polls: bool = True,
change_info: bool = True, change_info: bool = True,
invite_users: bool = True, invite_users: bool = True,
@ -986,6 +1003,12 @@ class ChatMethods:
send_inline (`bool`, optional): send_inline (`bool`, optional):
Whether the user is able to use inline bots or not. Whether the user is able to use inline bots or not.
embed_link_previews (`bool`, optional):
Whether the user is able to enable the link preview in the
messages they send. Note that the user will still be able to
send messages with links if this permission is removed, but
these links won't display a link preview.
send_polls (`bool`, optional): send_polls (`bool`, optional):
Whether the user is able to send polls or not. Whether the user is able to send polls or not.
@ -1031,6 +1054,7 @@ class ChatMethods:
send_gifs=not send_gifs, send_gifs=not send_gifs,
send_games=not send_games, send_games=not send_games,
send_inline=not send_inline, send_inline=not send_inline,
embed_links=not embed_link_previews,
send_polls=not send_polls, send_polls=not send_polls,
change_info=not change_info, change_info=not change_info,
invite_users=not invite_users, invite_users=not invite_users,
@ -1115,4 +1139,67 @@ class ChatMethods:
else: else:
raise ValueError('You must pass either a channel or a chat') raise ValueError('You must pass either a channel or a chat')
async def get_stats(
self: 'TelegramClient',
entity: 'hints.EntityLike',
):
"""
Retrieves statistics from the given megagroup or broadcast channel.
Note that some restrictions apply before being able to fetch statistics,
in particular the channel must have enough members (for megagroups, this
requires `at least 500 members`_).
Arguments
entity (`entity`):
The channel from which to get statistics.
Raises
If the given entity is not a channel (broadcast or megagroup),
a `TypeError` is raised.
If there are not enough members (poorly named) errors such as
``telethon.errors.ChatAdminRequiredError`` will appear.
Returns
Either :tl:`BroadcastStats` or :tl:`MegagroupStats`, depending on
whether the input belonged to a broadcast channel or megagroup.
Example
.. code-block:: python
# Some megagroup or channel username or ID to fetch
channel = -100123
stats = await client.get_stats(channel)
print('Stats from', stats.period.min_date, 'to', stats.period.max_date, ':')
print(stats.stringify())
.. _`at least 500 members`: https://telegram.org/blog/profile-videos-people-nearby-and-more
"""
entity = await self.get_input_entity(entity)
if helpers._entity_type(entity) != helpers._EntityType.CHANNEL:
raise TypeError('You must pass a user entity')
# Don't bother fetching the Channel entity (costs a request), instead
# try to guess and if it fails we know it's the other one (best case
# no extra request, worst just one).
try:
req = functions.stats.GetBroadcastStatsRequest(entity)
return await self(req)
except errors.StatsMigrateError as e:
dc = e.dc
except errors.BroadcastRequiredError:
req = functions.stats.GetMegagroupStatsRequest(entity)
try:
return await self(req)
except errors.StatsMigrateError as e:
dc = e.dc
sender = await self._borrow_exported_sender(dc)
try:
# req will be resolved to use the right types inside by now
return await sender.send(req)
finally:
await self._return_exported_sender(sender)
# endregion # endregion

View File

@ -245,7 +245,7 @@ class DialogMethods:
# Getting only archived dialogs (both equivalent) # Getting only archived dialogs (both equivalent)
archived = await client.get_dialogs(folder=1) archived = await client.get_dialogs(folder=1)
non_archived = await client.get_dialogs(archived=True) archived = await client.get_dialogs(archived=True)
""" """
return await self.iter_dialogs(*args, **kwargs).collect() return await self.iter_dialogs(*args, **kwargs).collect()
@ -378,7 +378,7 @@ class DialogMethods:
entities = [await self.get_input_entity(entity)] entities = [await self.get_input_entity(entity)]
else: else:
entities = await asyncio.gather( entities = await asyncio.gather(
*(self.get_input_entity(x) for x in entity), loop=self.loop) *(self.get_input_entity(x) for x in entity))
if folder is None: if folder is None:
raise ValueError('You must specify a folder') raise ValueError('You must specify a folder')

View File

@ -5,6 +5,8 @@ import pathlib
import typing import typing
import inspect import inspect
from ..crypto import AES
from .. import utils, helpers, errors, hints from .. import utils, helpers, errors, hints
from ..requestiter import RequestIter from ..requestiter import RequestIter
from ..tl import TLObject, types, functions from ..tl import TLObject, types, functions
@ -17,7 +19,6 @@ except ImportError:
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .telegramclient import TelegramClient from .telegramclient import TelegramClient
# Chunk sizes for upload.getFile must be multiples of the smallest size # Chunk sizes for upload.getFile must be multiples of the smallest size
MIN_CHUNK_SIZE = 4096 MIN_CHUNK_SIZE = 4096
MAX_CHUNK_SIZE = 512 * 1024 MAX_CHUNK_SIZE = 512 * 1024
@ -67,7 +68,7 @@ class _DirectDownloadIter(RequestIter):
async def _request(self): async def _request(self):
try: try:
result = await self._sender.send(self.request) result = await self.client._call(self._sender, self.request)
if isinstance(result, types.upload.FileCdnRedirect): if isinstance(result, types.upload.FileCdnRedirect):
raise NotImplementedError # TODO Implement raise NotImplementedError # TODO Implement
else: else:
@ -309,13 +310,20 @@ class DownloadMethods:
The parameter should be an integer index between ``0`` and The parameter should be an integer index between ``0`` and
``len(sizes)``. ``0`` will download the smallest thumbnail, ``len(sizes)``. ``0`` will download the smallest thumbnail,
and ``len(sizes) - 1`` will download the largest thumbnail. and ``len(sizes) - 1`` will download the largest thumbnail.
You can also use negative indices. You can also use negative indices, which work the same as
they do in Python's `list`.
You can also pass the :tl:`PhotoSize` instance to use. You can also pass the :tl:`PhotoSize` instance to use.
Alternatively, the thumb size type `str` may be used.
In short, use ``thumb=0`` if you want the smallest thumbnail In short, use ``thumb=0`` if you want the smallest thumbnail
and ``thumb=-1`` if you want the largest thumbnail. and ``thumb=-1`` if you want the largest thumbnail.
.. note::
The largest thumbnail may be a video instead of a photo,
as they are available since layer 116 and are bigger than
any of the photos.
Returns Returns
`None` if no media was provided, or if it was Empty. On success `None` if no media was provided, or if it was Empty. On success
the file path is returned since it may differ from the one given. the file path is returned since it may differ from the one given.
@ -328,6 +336,13 @@ class DownloadMethods:
# or # or
path = await message.download_media() path = await message.download_media()
await message.download_media(filename) await message.download_media(filename)
# Printing download progress
def callback(current, total):
print('Downloaded', current, 'out of', total,
'bytes: {:.2%}'.format(current / total))
await client.download_media(message, progress_callback=callback)
""" """
# TODO This won't work for messageService # TODO This won't work for messageService
if isinstance(message, types.Message): if isinstance(message, types.Message):
@ -369,10 +384,17 @@ class DownloadMethods:
part_size_kb: float = None, part_size_kb: float = None,
file_size: int = None, file_size: int = None,
progress_callback: 'hints.ProgressCallback' = None, progress_callback: 'hints.ProgressCallback' = None,
dc_id: int = None) -> typing.Optional[bytes]: dc_id: int = None,
key: bytes = None,
iv: bytes = None) -> typing.Optional[bytes]:
""" """
Low-level method to download files from their input location. Low-level method to download files from their input location.
.. note::
Generally, you should instead use `download_media`.
This method is intended to be a bit more low-level.
Arguments Arguments
input_location (:tl:`InputFileLocation`): input_location (:tl:`InputFileLocation`):
The file location from which the file will be downloaded. The file location from which the file will be downloaded.
@ -403,6 +425,13 @@ class DownloadMethods:
The data center the library should connect to in order The data center the library should connect to in order
to download the file. You shouldn't worry about this. to download the file. You shouldn't worry about this.
key ('bytes', optional):
In case of an encrypted upload (secret chats) a key is supplied
iv ('bytes', optional):
In case of an encrypted upload (secret chats) an iv is supplied
Example Example
.. code-block:: python .. code-block:: python
@ -421,6 +450,9 @@ class DownloadMethods:
raise ValueError( raise ValueError(
'The part size must be evenly divisible by 4096.') 'The part size must be evenly divisible by 4096.')
if isinstance(file, pathlib.Path):
file = str(file.absolute())
in_memory = file is None or file is bytes in_memory = file is None or file is bytes
if in_memory: if in_memory:
f = io.BytesIO() f = io.BytesIO()
@ -434,6 +466,8 @@ class DownloadMethods:
try: try:
async for chunk in self.iter_download( async for chunk in self.iter_download(
input_location, request_size=part_size, dc_id=dc_id): input_location, request_size=part_size, dc_id=dc_id):
if iv and key:
chunk = AES.decrypt_ige(chunk, key, iv)
r = f.write(chunk) r = f.write(chunk)
if inspect.isawaitable(r): if inspect.isawaitable(r):
await r await r
@ -535,25 +569,18 @@ class DownloadMethods:
# Streaming `media` to an output file # Streaming `media` to an output file
# After the iteration ends, the sender is cleaned up # After the iteration ends, the sender is cleaned up
with open('photo.jpg', 'wb') as fd: with open('photo.jpg', 'wb') as fd:
async for chunk client.iter_download(media): async for chunk in client.iter_download(media):
fd.write(chunk) fd.write(chunk)
# Fetching only the header of a file (32 bytes) # Fetching only the header of a file (32 bytes)
# You should manually close the iterator in this case. # You should manually close the iterator in this case.
# #
# telethon.sync must be imported for this to work, # "stream" is a common name for asynchronous generators,
# and you must not be inside an "async def". # and iter_download will yield `bytes` (chunks of the file).
stream = client.iter_download(media, request_size=32) stream = client.iter_download(media, request_size=32)
header = next(stream) header = await stream.__anext__() # "manual" version of `async for`
stream.close() await stream.close()
assert len(header) == 32 assert len(header) == 32
# Fetching only the header, inside of an ``async def``
async def main():
stream = client.iter_download(media, request_size=32)
header = await stream.__anext__()
await stream.close()
assert len(header) == 32
""" """
info = utils._get_file_info(file) info = utils._get_file_info(file)
if info.dc_id is not None: if info.dc_id is not None:
@ -610,12 +637,32 @@ class DownloadMethods:
@staticmethod @staticmethod
def _get_thumb(thumbs, thumb): def _get_thumb(thumbs, thumb):
# Seems Telegram has changed the order and put `PhotoStrippedSize`
# last while this is the smallest (layer 116). Ensure we have the
# sizes sorted correctly with a custom function.
def sort_thumbs(thumb):
if isinstance(thumb, types.PhotoStrippedSize):
return 1, len(thumb.bytes)
if isinstance(thumb, types.PhotoCachedSize):
return 1, len(thumb.bytes)
if isinstance(thumb, types.PhotoSize):
return 1, thumb.size
if isinstance(thumb, types.VideoSize):
return 2, thumb.size
# Empty size or invalid should go last
return 0, 0
thumbs = list(sorted(thumbs, key=sort_thumbs))
if thumb is None: if thumb is None:
return thumbs[-1] return thumbs[-1]
elif isinstance(thumb, int): elif isinstance(thumb, int):
return thumbs[thumb] return thumbs[thumb]
elif isinstance(thumb, str):
return next((t for t in thumbs if t.type == thumb), None)
elif isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize, elif isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize,
types.PhotoStrippedSize)): types.PhotoStrippedSize, types.VideoSize)):
return thumb return thumb
else: else:
return None return None
@ -650,11 +697,16 @@ class DownloadMethods:
if not isinstance(photo, types.Photo): if not isinstance(photo, types.Photo):
return return
size = self._get_thumb(photo.sizes, thumb) # Include video sizes here (but they may be None so provide an empty list)
size = self._get_thumb(photo.sizes + (photo.video_sizes or []), thumb)
if not size or isinstance(size, types.PhotoSizeEmpty): if not size or isinstance(size, types.PhotoSizeEmpty):
return return
file = self._get_proper_filename(file, 'photo', '.jpg', date=date) if isinstance(size, types.VideoSize):
file = self._get_proper_filename(file, 'video', '.mp4', date=date)
else:
file = self._get_proper_filename(file, 'photo', '.jpg', date=date)
if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)):
return self._download_cached_photo_size(size, file) return self._download_cached_photo_size(size, file)
@ -703,15 +755,15 @@ class DownloadMethods:
if not isinstance(document, types.Document): if not isinstance(document, types.Document):
return return
kind, possible_names = self._get_kind_and_names(document.attributes)
file = self._get_proper_filename(
file, kind, utils.get_extension(document),
date=date, possible_names=possible_names
)
if thumb is None: if thumb is None:
kind, possible_names = self._get_kind_and_names(document.attributes)
file = self._get_proper_filename(
file, kind, utils.get_extension(document),
date=date, possible_names=possible_names
)
size = None size = None
else: else:
file = self._get_proper_filename(file, 'photo', '.jpg', date=date)
size = self._get_thumb(document.thumbs, thumb) size = self._get_thumb(document.thumbs, thumb)
if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)): if isinstance(size, (types.PhotoCachedSize, types.PhotoStrippedSize)):
return self._download_cached_photo_size(size, file) return self._download_cached_photo_size(size, file)

View File

@ -131,7 +131,18 @@ class MessageParseMethods:
elif isinstance(update, ( elif isinstance(update, (
types.UpdateNewChannelMessage, types.UpdateNewMessage)): types.UpdateNewChannelMessage, types.UpdateNewMessage)):
update.message._finish_init(self, entities, input_chat) update.message._finish_init(self, entities, input_chat)
id_to_message[update.message.id] = update.message
# Pinning a message with `updatePinnedMessage` seems to
# always produce a service message we can't map so return
# it directly.
#
# It could also be a list (e.g. when sending albums).
#
# TODO this method is getting messier and messier as time goes on
if hasattr(request, 'random_id') or utils.is_list_like(request):
id_to_message[update.message.id] = update.message
else:
return update.message
elif (isinstance(update, types.UpdateEditMessage) elif (isinstance(update, types.UpdateEditMessage)
and helpers._entity_type(request.peer) != helpers._EntityType.CHANNEL): and helpers._entity_type(request.peer) != helpers._EntityType.CHANNEL):
@ -204,13 +215,18 @@ class MessageParseMethods:
# deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at # deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at
# Telegram), in which case we get some "missing" message mappings. # Telegram), in which case we get some "missing" message mappings.
# Log them with the hope that we can better work around them. # Log them with the hope that we can better work around them.
#
# This also happens when trying to forward messages that can't
# be forwarded because they don't exist (0, service, deleted)
# among others which could be (like deleted or existing).
self._log[__name__].warning( self._log[__name__].warning(
'Request %s had missing message mappings %s', request, result) 'Request %s had missing message mappings %s', request, result)
return [ return [
mapping.get(random_to_id.get(rnd)) (mapping.get(random_to_id[rnd]) or opposite.get(random_to_id[rnd]))
or opposite.get(random_to_id.get(rnd)) if rnd in random_to_id
for rnd in random_to_id else None
for rnd in random_id
] ]
# endregion # endregion

View File

@ -1025,14 +1025,25 @@ class MessageMethods:
force_document=force_document) force_document=force_document)
if isinstance(entity, types.InputBotInlineMessageID): if isinstance(entity, types.InputBotInlineMessageID):
return await self(functions.messages.EditInlineBotMessageRequest( request = functions.messages.EditInlineBotMessageRequest(
id=entity, id=entity,
message=text, message=text,
no_webpage=not link_preview, no_webpage=not link_preview,
entities=msg_entities, entities=msg_entities,
media=media, media=media,
reply_markup=self.build_reply_markup(buttons) reply_markup=self.build_reply_markup(buttons)
)) )
# Invoke `messages.editInlineBotMessage` from the right datacenter.
# Otherwise, Telegram will error with `MESSAGE_ID_INVALID` and do nothing.
exported = self.session.dc_id != entity.dc_id
if exported:
try:
sender = await self._borrow_exported_sender(entity.dc_id)
return await self._call(sender, request)
finally:
await self._return_exported_sender(sender)
else:
return await self(request)
entity = await self.get_input_entity(entity) entity = await self.get_input_entity(entity)
request = functions.messages.EditMessageRequest( request = functions.messages.EditMessageRequest(
@ -1046,7 +1057,6 @@ class MessageMethods:
schedule_date=schedule schedule_date=schedule
) )
msg = self._get_response_message(request, await self(request), entity) msg = self._get_response_message(request, await self(request), entity)
await self._cache_media(msg, file, file_handle, image=image)
return msg return msg
async def delete_messages( async def delete_messages(
@ -1108,8 +1118,14 @@ class MessageMethods:
else int(m) for m in message_ids else int(m) for m in message_ids
) )
entity = await self.get_input_entity(entity) if entity else None if entity:
if helpers._entity_type(entity) == helpers._EntityType.CHANNEL: entity = await self.get_input_entity(entity)
ty = helpers._entity_type(entity)
else:
# no entity (None), set a value that's not a channel for private delete
ty = helpers._EntityType.USER
if ty == helpers._EntityType.CHANNEL:
return await self([functions.channels.DeleteMessagesRequest( return await self([functions.channels.DeleteMessagesRequest(
entity, list(c)) for c in utils.chunks(message_ids)]) entity, list(c)) for c in utils.chunks(message_ids)])
else: else:
@ -1136,6 +1152,10 @@ class MessageMethods:
If neither message nor maximum ID are provided, all messages will be If neither message nor maximum ID are provided, all messages will be
marked as read by assuming that ``max_id = 0``. marked as read by assuming that ``max_id = 0``.
If a message or maximum ID is provided, all the messages up to and
including such ID will be marked as read (for all messages whose ID
max_id).
See also `Message.mark_read() <telethon.tl.custom.message.Message.mark_read>`. See also `Message.mark_read() <telethon.tl.custom.message.Message.mark_read>`.
Arguments Arguments
@ -1146,8 +1166,8 @@ class MessageMethods:
Either a list of messages or a single message. Either a list of messages or a single message.
max_id (`int`): max_id (`int`):
Overrides messages, until which message should the Until which message should the read acknowledge be sent for.
acknowledge should be sent. This has priority over the ``message`` parameter.
clear_mentions (`bool`): clear_mentions (`bool`):
Whether the mention badge should be cleared (so that Whether the mention badge should be cleared (so that
@ -1226,11 +1246,24 @@ class MessageMethods:
""" """
message = utils.get_message_id(message) or 0 message = utils.get_message_id(message) or 0
entity = await self.get_input_entity(entity) entity = await self.get_input_entity(entity)
await self(functions.messages.UpdatePinnedMessageRequest( request = functions.messages.UpdatePinnedMessageRequest(
peer=entity, peer=entity,
id=message, id=message,
silent=not notify silent=not notify
)) )
result = await self(request)
# Unpinning does not produce a service message, and technically
# users can pass negative IDs which seem to behave as unpinning too.
if message <= 0:
return
# Pinning in User chats (just with yourself really) does not produce a service message
if helpers._entity_type(entity) == helpers._EntityType.USER:
return
# Pinning a message that doesn't exist would RPC-error earlier
return self._get_response_message(request, result, entity)
# endregion # endregion

View File

@ -3,7 +3,6 @@ import asyncio
import collections import collections
import logging import logging
import platform import platform
import sys
import time import time
import typing import typing
@ -14,12 +13,12 @@ from ..extensions import markdown
from ..network import MTProtoSender, Connection, ConnectionTcpFull, TcpMTProxy from ..network import MTProtoSender, Connection, ConnectionTcpFull, TcpMTProxy
from ..sessions import Session, SQLiteSession, MemorySession from ..sessions import Session, SQLiteSession, MemorySession
from ..statecache import StateCache from ..statecache import StateCache
from ..tl import TLObject, functions, types from ..tl import functions, types
from ..tl.alltlobjects import LAYER from ..tl.alltlobjects import LAYER
DEFAULT_DC_ID = 2 DEFAULT_DC_ID = 2
DEFAULT_IPV4_IP = '149.154.167.51' DEFAULT_IPV4_IP = '149.154.167.51'
DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]' DEFAULT_IPV6_IP = '2001:67c:4e8:f002::a'
DEFAULT_PORT = 443 DEFAULT_PORT = 443
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
@ -29,6 +28,41 @@ __default_log__ = logging.getLogger(__base_name__)
__default_log__.addHandler(logging.NullHandler()) __default_log__.addHandler(logging.NullHandler())
# In seconds, how long to wait before disconnecting a exported sender.
_DISCONNECT_EXPORTED_AFTER = 60
class _ExportState:
def __init__(self):
# ``n`` is the amount of borrows a given sender has;
# once ``n`` reaches ``0``, disconnect the sender after a while.
self._n = 0
self._zero_ts = 0
self._connected = False
def add_borrow(self):
self._n += 1
self._connected = True
def add_return(self):
self._n -= 1
assert self._n >= 0, 'returned sender more than it was borrowed'
if self._n == 0:
self._zero_ts = time.time()
def should_disconnect(self):
return (self._n == 0
and self._connected
and (time.time() - self._zero_ts) > _DISCONNECT_EXPORTED_AFTER)
def need_connect(self):
return not self._connected
def mark_disconnected(self):
assert self.should_disconnect(), 'marked as disconnected when it was borrowed'
self._connected = False
# TODO How hard would it be to support both `trio` and `asyncio`? # TODO How hard would it be to support both `trio` and `asyncio`?
class TelegramBaseClient(abc.ABC): class TelegramBaseClient(abc.ABC):
""" """
@ -146,7 +180,8 @@ class TelegramBaseClient(abc.ABC):
Defaults to `lang_code`. Defaults to `lang_code`.
loop (`asyncio.AbstractEventLoop`, optional): loop (`asyncio.AbstractEventLoop`, optional):
Asyncio event loop to use. Defaults to `asyncio.get_event_loop()` Asyncio event loop to use. Defaults to `asyncio.get_event_loop()`.
This argument is ignored.
base_logger (`str` | `logging.Logger`, optional): base_logger (`str` | `logging.Logger`, optional):
Base logger name or instance to use. Base logger name or instance to use.
@ -193,7 +228,7 @@ class TelegramBaseClient(abc.ABC):
"Refer to telethon.rtfd.io for more information.") "Refer to telethon.rtfd.io for more information.")
self._use_ipv6 = use_ipv6 self._use_ipv6 = use_ipv6
self._loop = loop or asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
if isinstance(base_logger, str): if isinstance(base_logger, str):
base_logger = logging.getLogger(base_logger) base_logger = logging.getLogger(base_logger)
@ -300,7 +335,7 @@ class TelegramBaseClient(abc.ABC):
) )
self._sender = MTProtoSender( self._sender = MTProtoSender(
self.session.auth_key, self._loop, self.session.auth_key,
loggers=self._log, loggers=self._log,
retries=self._connection_retries, retries=self._connection_retries,
delay=self._retry_delay, delay=self._retry_delay,
@ -314,19 +349,17 @@ class TelegramBaseClient(abc.ABC):
# Remember flood-waited requests to avoid making them again # Remember flood-waited requests to avoid making them again
self._flood_waited_requests = {} self._flood_waited_requests = {}
# Cache ``{dc_id: (n, MTProtoSender)}`` for all borrowed senders, # Cache ``{dc_id: (_ExportState, MTProtoSender)}`` for all borrowed senders
# being ``n`` the amount of borrows a given sender has; once ``n``
# reaches ``0`` it should be disconnected and removed.
self._borrowed_senders = {} self._borrowed_senders = {}
self._borrow_sender_lock = asyncio.Lock(loop=self._loop) self._borrow_sender_lock = asyncio.Lock()
self._updates_handle = None self._updates_handle = None
self._last_request = time.time() self._last_request = time.time()
self._channel_pts = {} self._channel_pts = {}
if sequential_updates: if sequential_updates:
self._updates_queue = asyncio.Queue(loop=self._loop) self._updates_queue = asyncio.Queue()
self._dispatching_updates_queue = asyncio.Event(loop=self._loop) self._dispatching_updates_queue = asyncio.Event()
else: else:
# Use a set of pending instead of a queue so we can properly # Use a set of pending instead of a queue so we can properly
# terminate all pending updates on disconnect. # terminate all pending updates on disconnect.
@ -346,6 +379,15 @@ class TelegramBaseClient(abc.ABC):
# {chat_id: {Conversation}} # {chat_id: {Conversation}}
self._conversations = collections.defaultdict(set) self._conversations = collections.defaultdict(set)
# Hack to workaround the fact Telegram may send album updates as
# different Updates when being sent from a different data center.
# {grouped_id: AlbumHack}
#
# FIXME: We don't bother cleaning this up because it's not really
# worth it, albums are pretty rare and this only holds them
# for a second at most.
self._albums = {}
# Default parse mode # Default parse mode
self._parse_mode = markdown self._parse_mode = markdown
@ -375,7 +417,7 @@ class TelegramBaseClient(abc.ABC):
.. code-block:: python .. code-block:: python
# Download media in the background # Download media in the background
task = client.loop_create_task(message.download_media()) task = client.loop.create_task(message.download_media())
# Do some work # Do some work
... ...
@ -440,7 +482,6 @@ class TelegramBaseClient(abc.ABC):
self.session.server_address, self.session.server_address,
self.session.port, self.session.port,
self.session.dc_id, self.session.dc_id,
loop=self._loop,
loggers=self._log, loggers=self._log,
proxy=self._proxy proxy=self._proxy
)): )):
@ -500,13 +541,22 @@ class TelegramBaseClient(abc.ABC):
async def _disconnect_coro(self: 'TelegramClient'): async def _disconnect_coro(self: 'TelegramClient'):
await self._disconnect() await self._disconnect()
# Also clean-up all exported senders because we're done with them
async with self._borrow_sender_lock:
for state, sender in self._borrowed_senders.values():
if state.should_disconnect():
# disconnect should never raise
await sender.disconnect()
self._borrowed_senders.clear()
# trio's nurseries would handle this for us, but this is asyncio. # trio's nurseries would handle this for us, but this is asyncio.
# All tasks spawned in the background should properly be terminated. # All tasks spawned in the background should properly be terminated.
if self._dispatching_updates_queue is None and self._updates_queue: if self._dispatching_updates_queue is None and self._updates_queue:
for task in self._updates_queue: for task in self._updates_queue:
task.cancel() task.cancel()
await asyncio.wait(self._updates_queue, loop=self._loop) await asyncio.wait(self._updates_queue)
self._updates_queue.clear() self._updates_queue.clear()
pts, date = self._state_cache[None] pts, date = self._state_cache[None]
@ -589,17 +639,15 @@ class TelegramBaseClient(abc.ABC):
# #
# If one were to do that, Telegram would reset the connection # If one were to do that, Telegram would reset the connection
# with no further clues. # with no further clues.
sender = MTProtoSender(None, self._loop, loggers=self._log) sender = MTProtoSender(None, loggers=self._log)
await sender.connect(self._connection( await sender.connect(self._connection(
dc.ip_address, dc.ip_address,
dc.port, dc.port,
dc.id, dc.id,
loop=self._loop,
loggers=self._log, loggers=self._log,
proxy=self._proxy proxy=self._proxy
)) ))
self._log[__name__].info('Exporting authorization for data center %s', self._log[__name__].info('Exporting auth for new borrowed sender in %s', dc)
dc)
auth = await self(functions.auth.ExportAuthorizationRequest(dc_id)) auth = await self(functions.auth.ExportAuthorizationRequest(dc_id))
req = self._init_with(functions.auth.ImportAuthorizationRequest( req = self._init_with(functions.auth.ImportAuthorizationRequest(
id=auth.id, bytes=auth.bytes id=auth.id, bytes=auth.bytes
@ -616,24 +664,27 @@ class TelegramBaseClient(abc.ABC):
Once its job is over it should be `_return_exported_sender`. Once its job is over it should be `_return_exported_sender`.
""" """
async with self._borrow_sender_lock: async with self._borrow_sender_lock:
n, sender = self._borrowed_senders.get(dc_id, (0, None)) self._log[__name__].debug('Borrowing sender for dc_id %d', dc_id)
if not sender: state, sender = self._borrowed_senders.get(dc_id, (None, None))
if state is None:
state = _ExportState()
sender = await self._create_exported_sender(dc_id) sender = await self._create_exported_sender(dc_id)
sender.dc_id = dc_id sender.dc_id = dc_id
elif not n: self._borrowed_senders[dc_id] = (state, sender)
elif state.need_connect():
dc = await self._get_dc(dc_id) dc = await self._get_dc(dc_id)
await sender.connect(self._connection( await sender.connect(self._connection(
dc.ip_address, dc.ip_address,
dc.port, dc.port,
dc.id, dc.id,
loop=self._loop,
loggers=self._log, loggers=self._log,
proxy=self._proxy proxy=self._proxy
)) ))
self._borrowed_senders[dc_id] = (n + 1, sender) state.add_borrow()
return sender
return sender
async def _return_exported_sender(self: 'TelegramClient', sender): async def _return_exported_sender(self: 'TelegramClient', sender):
""" """
@ -641,14 +692,23 @@ class TelegramBaseClient(abc.ABC):
been returned, the sender is cleanly disconnected. been returned, the sender is cleanly disconnected.
""" """
async with self._borrow_sender_lock: async with self._borrow_sender_lock:
dc_id = sender.dc_id self._log[__name__].debug('Returning borrowed sender for dc_id %d', sender.dc_id)
n, _ = self._borrowed_senders[dc_id] state, _ = self._borrowed_senders[sender.dc_id]
n -= 1 state.add_return()
self._borrowed_senders[dc_id] = (n, sender)
if not n: async def _clean_exported_senders(self: 'TelegramClient'):
self._log[__name__].info( """
'Disconnecting borrowed sender for DC %d', dc_id) Cleans-up all unused exported senders by disconnecting them.
await sender.disconnect() """
async with self._borrow_sender_lock:
for dc_id, (state, sender) in self._borrowed_senders.items():
if state.should_disconnect():
self._log[__name__].info(
'Disconnecting borrowed sender for DC %d', dc_id)
# Disconnect should never raise
await sender.disconnect()
state.mark_disconnected()
async def _get_cdn_client(self: 'TelegramClient', cdn_redirect): async def _get_cdn_client(self: 'TelegramClient', cdn_redirect):
"""Similar to ._borrow_exported_client, but for CDNs""" """Similar to ._borrow_exported_client, but for CDNs"""

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import inspect
import itertools import itertools
import random import random
import time import time
@ -325,7 +326,7 @@ class UpdateMethods:
while self.is_connected(): while self.is_connected():
try: try:
await asyncio.wait_for( await asyncio.wait_for(
self.disconnected, timeout=60, loop=self._loop self.disconnected, timeout=60
) )
continue # We actually just want to act upon timeout continue # We actually just want to act upon timeout
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -335,6 +336,9 @@ class UpdateMethods:
except Exception: except Exception:
continue # Any disconnected exception should be ignored continue # Any disconnected exception should be ignored
# Check if we have any exported senders to clean-up periodically
await self._clean_exported_senders()
# Don't bother sending pings until the low-level connection is # Don't bother sending pings until the low-level connection is
# ready, otherwise a lot of pings will be batched to be sent upon # ready, otherwise a lot of pings will be batched to be sent upon
# reconnect, when we really don't care about that. # reconnect, when we really don't care about that.
@ -388,11 +392,19 @@ class UpdateMethods:
await self._get_difference(update, channel_id, pts_date) await self._get_difference(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:
# 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: if not self._self_input_peer:
# Some updates require our own ID, so we must make sure # Some updates require our own ID, so we must make sure
# that the event builder has offline access to it. Calling # that the event builder has offline access to it. Calling
# `get_me()` will cache it under `self._self_input_peer`. # `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) await self.get_me(input_peer=True)
built = EventBuilderDict(self, update, others) built = EventBuilderDict(self, update, others)
@ -421,7 +433,50 @@ class UpdateMethods:
if not builder.resolved: if not builder.resolved:
await builder.resolve(self) await builder.resolve(self)
if not builder.filter(event): 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 _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 continue
try: try:
@ -559,8 +614,15 @@ class EventBuilderDict:
try: try:
return self.__dict__[builder] return self.__dict__[builder]
except KeyError: 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( 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): if isinstance(event, EventCommon):
event.original_update = self.update event.original_update = self.update

View File

@ -5,9 +5,10 @@ import os
import pathlib import pathlib
import re import re
import typing import typing
import inspect
from io import BytesIO from io import BytesIO
from ..crypto import AES
from .. import utils, helpers, hints from .. import utils, helpers, hints
from ..tl import types, functions, custom from ..tl import types, functions, custom
@ -93,6 +94,7 @@ class UploadMethods:
*, *,
caption: typing.Union[str, typing.Sequence[str]] = None, caption: typing.Union[str, typing.Sequence[str]] = None,
force_document: bool = False, force_document: bool = False,
file_size: int = None,
clear_draft: bool = False, clear_draft: bool = False,
progress_callback: 'hints.ProgressCallback' = None, progress_callback: 'hints.ProgressCallback' = None,
reply_to: 'hints.MessageIDLike' = None, reply_to: 'hints.MessageIDLike' = None,
@ -153,6 +155,10 @@ class UploadMethods:
* A handle to an uploaded file (from `upload_file`). * A handle to an uploaded file (from `upload_file`).
* A :tl:`InputMedia` instance. For example, if you want to
send a dice use :tl:`InputMediaDice`, or if you want to
send a contact use :tl:`InputMediaContact`.
To send an album, you should provide a list in this parameter. To send an album, you should provide a list in this parameter.
If a list or similar is provided, the files in it will be If a list or similar is provided, the files in it will be
@ -169,6 +175,13 @@ class UploadMethods:
the extension of an image file or a video file, it will be the extension of an image file or a video file, it will be
sent as such. Otherwise always as a document. sent as such. Otherwise always as a document.
file_size (`int`, optional):
The size of the file to be uploaded if it needs to be uploaded,
which will be determined automatically if not specified.
If the file size can't be determined beforehand, the entire
file will be read in-memory to find out how large it is.
clear_draft (`bool`, optional): clear_draft (`bool`, optional):
Whether the existing draft should be cleared or not. Whether the existing draft should be cleared or not.
@ -261,6 +274,26 @@ class UploadMethods:
'/my/photos/holiday2.jpg', '/my/photos/holiday2.jpg',
'/my/drawings/portrait.png' '/my/drawings/portrait.png'
]) ])
# Printing upload progress
def callback(current, total):
print('Uploaded', current, 'out of', total,
'bytes: {:.2%}'.format(current / total))
await client.send_file(chat, file, progress_callback=callback)
# Dices, including dart and other future emoji
from telethon.tl import types
await client.send_file(chat, types.InputMediaDice(''))
await client.send_file(chat, types.InputMediaDice('🎯'))
# Contacts
await client.send_file(chat, types.InputMediaContact(
phone_number='+34 123 456 789',
first_name='Example',
last_name='',
vcard=''
))
""" """
# TODO Properly implement allow_cache to reuse the sha256 of the file # TODO Properly implement allow_cache to reuse the sha256 of the file
# i.e. `None` was used # i.e. `None` was used
@ -332,6 +365,7 @@ class UploadMethods:
file_handle, media, image = await self._file_to_media( file_handle, media, image = await self._file_to_media(
file, force_document=force_document, file, force_document=force_document,
file_size=file_size,
progress_callback=progress_callback, progress_callback=progress_callback,
attributes=attributes, allow_cache=allow_cache, thumb=thumb, attributes=attributes, allow_cache=allow_cache, thumb=thumb,
voice_note=voice_note, video_note=video_note, voice_note=voice_note, video_note=video_note,
@ -348,10 +382,7 @@ class UploadMethods:
entities=msg_entities, reply_markup=markup, silent=silent, entities=msg_entities, reply_markup=markup, silent=silent,
schedule_date=schedule, clear_draft=clear_draft schedule_date=schedule, clear_draft=clear_draft
) )
msg = self._get_response_message(request, await self(request), entity) return self._get_response_message(request, await self(request), entity)
await self._cache_media(msg, file, file_handle, image=image)
return msg
async def _send_album(self: 'TelegramClient', entity, files, caption='', async def _send_album(self: 'TelegramClient', entity, files, caption='',
progress_callback=None, reply_to=None, progress_callback=None, reply_to=None,
@ -390,16 +421,12 @@ class UploadMethods:
r = await self(functions.messages.UploadMediaRequest( r = await self(functions.messages.UploadMediaRequest(
entity, media=fm entity, media=fm
)) ))
self.session.cache_file(
fh.md5, fh.size, utils.get_input_photo(r.photo))
fm = utils.get_input_media(r.photo) fm = utils.get_input_media(r.photo)
elif isinstance(fm, types.InputMediaUploadedDocument): elif isinstance(fm, types.InputMediaUploadedDocument):
r = await self(functions.messages.UploadMediaRequest( r = await self(functions.messages.UploadMediaRequest(
entity, media=fm entity, media=fm
)) ))
self.session.cache_file(
fh.md5, fh.size, utils.get_input_document(r.document))
fm = utils.get_input_media( fm = utils.get_input_media(
r.document, supports_streaming=supports_streaming) r.document, supports_streaming=supports_streaming)
@ -430,12 +457,19 @@ class UploadMethods:
file: 'hints.FileLike', file: 'hints.FileLike',
*, *,
part_size_kb: float = None, part_size_kb: float = None,
file_size: int = None,
file_name: str = None, file_name: str = None,
use_cache: type = None, use_cache: type = None,
key: bytes = None,
iv: bytes = None,
progress_callback: 'hints.ProgressCallback' = None) -> 'types.TypeInputFile': progress_callback: 'hints.ProgressCallback' = None) -> 'types.TypeInputFile':
""" """
Uploads a file to Telegram's servers, without sending it. Uploads a file to Telegram's servers, without sending it.
.. note::
Generally, you want to use `send_file` instead.
This method returns a handle (an instance of :tl:`InputFile` or This method returns a handle (an instance of :tl:`InputFile` or
:tl:`InputFileBig`, as required) which can be later used before :tl:`InputFileBig`, as required) which can be later used before
it expires (they are usable during less than a day). it expires (they are usable during less than a day).
@ -455,6 +489,13 @@ class UploadMethods:
Chunk size when uploading files. The larger, the less Chunk size when uploading files. The larger, the less
requests will be made (up to 512KB maximum). requests will be made (up to 512KB maximum).
file_size (`int`, optional):
The size of the file to be uploaded, which will be determined
automatically if not specified.
If the file size can't be determined beforehand, the entire
file will be read in-memory to find out how large it is.
file_name (`str`, optional): file_name (`str`, optional):
The file name which will be used on the resulting InputFile. The file name which will be used on the resulting InputFile.
If not specified, the name will be taken from the ``file`` If not specified, the name will be taken from the ``file``
@ -465,6 +506,12 @@ class UploadMethods:
backward-compatibility (and it may get its use back in backward-compatibility (and it may get its use back in
the future). the future).
key ('bytes', optional):
In case of an encrypted upload (secret chats) a key is supplied
iv ('bytes', optional):
In case of an encrypted upload (secret chats) an iv is supplied
progress_callback (`callable`, optional): progress_callback (`callable`, optional):
A callback function accepting two parameters: A callback function accepting two parameters:
``(sent bytes, total)``. ``(sent bytes, total)``.
@ -496,34 +543,42 @@ class UploadMethods:
if not file_name and getattr(file, 'name', None): if not file_name and getattr(file, 'name', None):
file_name = file.name file_name = file.name
if isinstance(file, str): if file_size is not None:
pass # do nothing as it's already kwown
elif isinstance(file, str):
file_size = os.path.getsize(file) file_size = os.path.getsize(file)
stream = open(file, 'rb')
close_stream = True
elif isinstance(file, bytes): elif isinstance(file, bytes):
file_size = len(file) file_size = len(file)
stream = io.BytesIO(file)
close_stream = True
else: else:
# `aiofiles` shouldn't base `IOBase` because they change the if not callable(getattr(file, 'read', None)):
# methods' definition. `seekable` would be `async` but since raise TypeError('file description should have a `read` method')
# we won't get to check that, there's no need to maybe-await.
if isinstance(file, io.IOBase) and file.seekable(): if callable(getattr(file, 'seekable', None)):
pos = file.tell() seekable = await helpers._maybe_await(file.seekable())
else: else:
pos = None seekable = False
# TODO Don't load the entire file in memory always if seekable:
data = file.read() pos = await helpers._maybe_await(file.tell())
if inspect.isawaitable(data): await helpers._maybe_await(file.seek(0, os.SEEK_END))
data = await data file_size = await helpers._maybe_await(file.tell())
await helpers._maybe_await(file.seek(pos, os.SEEK_SET))
if pos is not None: stream = file
file.seek(pos) close_stream = False
else:
self._log[__name__].warning(
'Could not determine file size beforehand so the entire '
'file will be read in-memory')
if not isinstance(data, bytes): data = await helpers._maybe_await(file.read())
raise TypeError( stream = io.BytesIO(data)
'file descriptor returned {}, not bytes (you must ' close_stream = True
'open the file in bytes mode)'.format(type(data))) file_size = len(data)
file = data
file_size = len(file)
# File will now either be a string or bytes # File will now either be a string or bytes
if not part_size_kb: if not part_size_kb:
@ -553,31 +608,46 @@ class UploadMethods:
# Determine whether the file is too big (over 10MB) or not # Determine whether the file is too big (over 10MB) or not
# Telegram does make a distinction between smaller or larger files # Telegram does make a distinction between smaller or larger files
is_large = file_size > 10 * 1024 * 1024 is_big = file_size > 10 * 1024 * 1024
hash_md5 = hashlib.md5() hash_md5 = hashlib.md5()
if not is_large:
# Calculate the MD5 hash before anything else.
# As this needs to be done always for small files,
# might as well do it before anything else and
# check the cache.
if isinstance(file, str):
with open(file, 'rb') as stream:
file = stream.read()
hash_md5.update(file)
part_count = (file_size + part_size - 1) // part_size part_count = (file_size + part_size - 1) // part_size
self._log[__name__].info('Uploading file of %d bytes in %d chunks of %d', self._log[__name__].info('Uploading file of %d bytes in %d chunks of %d',
file_size, part_count, part_size) file_size, part_count, part_size)
with open(file, 'rb') if isinstance(file, str) else BytesIO(file)\ pos = 0
as stream: try:
for part_index in range(part_count): for part_index in range(part_count):
# Read the file by in chunks of size part_size # Read the file by in chunks of size part_size
part = stream.read(part_size) part = await helpers._maybe_await(stream.read(part_size))
if not isinstance(part, bytes):
raise TypeError(
'file descriptor returned {}, not bytes (you must '
'open the file in bytes mode)'.format(type(part)))
# `file_size` could be wrong in which case `part` may not be
# `part_size` before reaching the end.
if len(part) != part_size and part_index < part_count - 1:
raise ValueError(
'read less than {} before reaching the end; either '
'`file_size` or `read` are wrong'.format(part_size))
pos += len(part)
if not is_big:
# Bit odd that MD5 is only needed for small files and not
# big ones with more chance for corruption, but that's
# what Telegram wants.
hash_md5.update(part)
# Encryption part if needed
if key and iv:
part = AES.encrypt_ige(part, key, iv)
# The SavePartRequest is different depending on whether # The SavePartRequest is different depending on whether
# the file is too large or not (over or less than 10MB) # the file is too large or not (over or less than 10MB)
if is_large: if is_big:
request = functions.upload.SaveBigFilePartRequest( request = functions.upload.SaveBigFilePartRequest(
file_id, part_index, part_count, part) file_id, part_index, part_count, part)
else: else:
@ -589,14 +659,15 @@ class UploadMethods:
self._log[__name__].debug('Uploaded %d/%d', self._log[__name__].debug('Uploaded %d/%d',
part_index + 1, part_count) part_index + 1, part_count)
if progress_callback: if progress_callback:
r = progress_callback(stream.tell(), file_size) await helpers._maybe_await(progress_callback(pos, file_size))
if inspect.isawaitable(r):
await r
else: else:
raise RuntimeError( raise RuntimeError(
'Failed to upload file part {}.'.format(part_index)) 'Failed to upload file part {}.'.format(part_index))
finally:
if close_stream:
await helpers._maybe_await(stream.close())
if is_large: if is_big:
return types.InputFileBig(file_id, part_count, file_name) return types.InputFileBig(file_id, part_count, file_name)
else: else:
return custom.InputSizedFile( return custom.InputSizedFile(
@ -606,7 +677,7 @@ class UploadMethods:
# endregion # endregion
async def _file_to_media( async def _file_to_media(
self, file, force_document=False, self, file, force_document=False, file_size=None,
progress_callback=None, attributes=None, thumb=None, progress_callback=None, attributes=None, thumb=None,
allow_cache=True, voice_note=False, video_note=False, allow_cache=True, voice_note=False, video_note=False,
supports_streaming=False, mime_type=None, as_image=None): supports_streaming=False, mime_type=None, as_image=None):
@ -616,12 +687,14 @@ class UploadMethods:
if isinstance(file, pathlib.Path): if isinstance(file, pathlib.Path):
file = str(file.absolute()) file = str(file.absolute())
is_image = utils.is_image(file)
if as_image is None: if as_image is None:
as_image = utils.is_image(file) and not force_document as_image = is_image and not force_document
# `aiofiles` do not base `io.IOBase` but do have `read`, so we # `aiofiles` do not base `io.IOBase` but do have `read`, so we
# just check for the read attribute to see if it's file-like. # just check for the read attribute to see if it's file-like.
if not isinstance(file, (str, bytes)) and not hasattr(file, 'read'): if not isinstance(file, (str, bytes, types.InputFile, types.InputFileBig))\
and not hasattr(file, 'read'):
# The user may pass a Message containing media (or the media, # The user may pass a Message containing media (or the media,
# or anything similar) that should be treated as a file. Try # or anything similar) that should be treated as a file. Try
# getting the input media for whatever they passed and send it. # getting the input media for whatever they passed and send it.
@ -644,16 +717,18 @@ class UploadMethods:
media = None media = None
file_handle = None file_handle = None
if not isinstance(file, str) or os.path.isfile(file):
if isinstance(file, (types.InputFile, types.InputFileBig)):
file_handle = file
elif not isinstance(file, str) or os.path.isfile(file):
file_handle = await self.upload_file( file_handle = await self.upload_file(
_resize_photo_if_needed(file, as_image), _resize_photo_if_needed(file, as_image),
file_size=file_size,
progress_callback=progress_callback progress_callback=progress_callback
) )
elif re.match('https?://', file): elif re.match('https?://', file):
if as_image: if as_image:
media = types.InputMediaPhotoExternal(file) media = types.InputMediaPhotoExternal(file)
elif not force_document and utils.is_gif(file):
media = types.InputMediaGifExternal(file, '')
else: else:
media = types.InputMediaDocumentExternal(file) media = types.InputMediaDocumentExternal(file)
else: else:
@ -675,36 +750,26 @@ class UploadMethods:
file, file,
mime_type=mime_type, mime_type=mime_type,
attributes=attributes, attributes=attributes,
force_document=force_document, force_document=force_document and not is_image,
voice_note=voice_note, voice_note=voice_note,
video_note=video_note, video_note=video_note,
supports_streaming=supports_streaming supports_streaming=supports_streaming
) )
input_kw = {} if not thumb:
if thumb: thumb = None
else:
if isinstance(thumb, pathlib.Path): if isinstance(thumb, pathlib.Path):
thumb = str(thumb.absolute()) thumb = str(thumb.absolute())
input_kw['thumb'] = await self.upload_file(thumb) thumb = await self.upload_file(thumb, file_size=file_size)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
file=file_handle, file=file_handle,
mime_type=mime_type, mime_type=mime_type,
attributes=attributes, attributes=attributes,
**input_kw thumb=thumb,
force_file=force_document and not is_image
) )
return file_handle, media, as_image return file_handle, media, as_image
async def _cache_media(self: 'TelegramClient', msg, file, file_handle, image):
if file and msg and isinstance(file_handle,
custom.InputSizedFile):
# There was a response message and we didn't use cached
# version, so cache whatever we just sent to the database.
md5, size = file_handle.md5, file_handle.size
if image:
to_cache = utils.get_input_photo(msg.media.photo)
else:
to_cache = utils.get_input_document(msg.media.document)
self.session.cache_file(md5, size, to_cache)
# endregion # endregion

View File

@ -27,6 +27,9 @@ def _fmt_flood(delay, request, *, early=False, td=datetime.timedelta):
class UserMethods: class UserMethods:
async def __call__(self: 'TelegramClient', request, ordered=False): async def __call__(self: 'TelegramClient', request, ordered=False):
return await self._call(self._sender, request, ordered=ordered)
async def _call(self: 'TelegramClient', sender, request, ordered=False):
requests = (request if utils.is_list_like(request) else (request,)) requests = (request if utils.is_list_like(request) else (request,))
for r in requests: for r in requests:
if not isinstance(r, TLRequest): if not isinstance(r, TLRequest):
@ -41,7 +44,7 @@ class UserMethods:
self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None) self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None)
elif diff <= self.flood_sleep_threshold: elif diff <= self.flood_sleep_threshold:
self._log[__name__].info(*_fmt_flood(diff, r, early=True)) self._log[__name__].info(*_fmt_flood(diff, r, early=True))
await asyncio.sleep(diff, loop=self._loop) 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.FloodWaitError(request=r, capture=diff)
@ -50,7 +53,7 @@ class UserMethods:
self._last_request = time.time() self._last_request = time.time()
for attempt in retry_range(self._request_retries): for attempt in retry_range(self._request_retries):
try: try:
future = self._sender.send(request, ordered=ordered) future = sender.send(request, ordered=ordered)
if isinstance(future, list): if isinstance(future, list):
results = [] results = []
exceptions = [] exceptions = []
@ -76,7 +79,8 @@ class UserMethods:
self._entity_cache.add(result) self._entity_cache.add(result)
return result return result
except (errors.ServerError, errors.RpcCallFailError, except (errors.ServerError, errors.RpcCallFailError,
errors.RpcMcgetFailError) as e: errors.RpcMcgetFailError, errors.InterdcCallErrorError,
errors.InterdcCallRichErrorError) as 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)
@ -89,9 +93,14 @@ class UserMethods:
self._flood_waited_requests\ self._flood_waited_requests\
[request.CONSTRUCTOR_ID] = time.time() + e.seconds [request.CONSTRUCTOR_ID] = time.time() + e.seconds
# In test servers, FLOOD_WAIT_0 has been observed, and sleeping for
# such a short amount will cause retries very fast leading to issues.
if e.seconds == 0:
e.seconds = 1
if e.seconds <= self.flood_sleep_threshold: if e.seconds <= self.flood_sleep_threshold:
self._log[__name__].info(*_fmt_flood(e.seconds, request)) self._log[__name__].info(*_fmt_flood(e.seconds, request))
await asyncio.sleep(e.seconds, loop=self._loop) await asyncio.sleep(e.seconds)
else: else:
raise raise
except (errors.PhoneMigrateError, errors.NetworkMigrateError, except (errors.PhoneMigrateError, errors.NetworkMigrateError,

View File

@ -1,4 +1,6 @@
import asyncio
import time import time
import weakref
from .common import EventBuilder, EventCommon, name_inner_event from .common import EventBuilder, EventCommon, name_inner_event
from .. import utils from .. import utils
@ -14,6 +16,54 @@ _IGNORE_MAX_AGE = 5 # seconds
_IGNORE_DICT = {} _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 @name_inner_event
class Album(EventBuilder): class Album(EventBuilder):
""" """
@ -66,6 +116,7 @@ class Album(EventBuilder):
return return
# Check if the ignore list is too big, and if it is clean it # 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() now = time.time()
if len(_IGNORE_DICT) > _IGNORE_MAX_SIZE: if len(_IGNORE_DICT) > _IGNORE_MAX_SIZE:
for i in [i for i, t in _IGNORE_DICT.items() if now - t > _IGNORE_MAX_AGE]: for i in [i for i, t in _IGNORE_DICT.items() if now - t > _IGNORE_MAX_AGE]:
@ -84,6 +135,11 @@ class Album(EventBuilder):
and u.message.grouped_id == group) 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): class Event(EventCommon, SenderGetter):
""" """
Represents the event of a new album. Represents the event of a new album.
@ -115,6 +171,14 @@ class Album(EventBuilder):
for msg in self.messages: for msg in self.messages:
msg._finish_init(client, self._entities, None) 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 @property
def grouped_id(self): def grouped_id(self):
""" """
@ -259,7 +323,7 @@ class Album(EventBuilder):
`telethon.client.messages.MessageMethods.pin_message` `telethon.client.messages.MessageMethods.pin_message`
with both ``entity`` and ``message`` already set. with both ``entity`` and ``message`` already set.
""" """
await self.messages[0].pin(notify=notify) return await self.messages[0].pin(notify=notify)
def __len__(self): def __len__(self):
""" """

View File

@ -51,7 +51,7 @@ class CallbackQuery(EventBuilder):
# Send a message with buttons users can click # Send a message with buttons users can click
async def main(): async def main():
await client.send_message(user, 'Yes or no?', buttons=[ await client.send_message(user, 'Yes or no?', buttons=[
Button.inline('Yes!', b'yes') Button.inline('Yes!', b'yes'),
Button.inline('Nope', b'no') Button.inline('Nope', b'no')
]) ])
""" """
@ -118,8 +118,10 @@ class CallbackQuery(EventBuilder):
elif event.query.data != self.match: elif event.query.data != self.match:
return return
if not self.func or self.func(event): if self.func:
return event # Return the result of func directly as it may need to be awaited
return self.func(event)
return True
class Event(EventCommon, SenderGetter): class Event(EventCommon, SenderGetter):
""" """

View File

@ -1,6 +1,6 @@
from .common import EventBuilder, EventCommon, name_inner_event from .common import EventBuilder, EventCommon, name_inner_event
from .. import utils from .. import utils
from ..tl import types, functions from ..tl import types
@name_inner_event @name_inner_event
@ -38,6 +38,10 @@ class ChatAction(EventBuilder):
return cls.Event(types.PeerChannel(update.channel_id), return cls.Event(types.PeerChannel(update.channel_id),
unpin=True) unpin=True)
elif isinstance(update, types.UpdateChatPinnedMessage) and update.id == 0:
return cls.Event(types.PeerChat(update.chat_id),
unpin=True)
elif isinstance(update, types.UpdateChatParticipantAdd): elif isinstance(update, types.UpdateChatParticipantAdd):
return cls.Event(types.PeerChat(update.chat_id), return cls.Event(types.PeerChat(update.chat_id),
added_by=update.inviter_id or True, added_by=update.inviter_id or True,
@ -104,8 +108,9 @@ class ChatAction(EventBuilder):
return cls.Event(msg, return cls.Event(msg,
users=msg.from_id, users=msg.from_id,
new_photo=True) new_photo=True)
elif isinstance(action, types.MessageActionPinMessage): elif isinstance(action, types.MessageActionPinMessage) and msg.reply_to_msg_id:
# Telegram always sends this service message for new pins # Seems to not be reliable on unpins, but when pinning
# we prefer this because we know who caused it.
return cls.Event(msg, return cls.Event(msg,
users=msg.from_id, users=msg.from_id,
new_pin=msg.reply_to_msg_id) new_pin=msg.reply_to_msg_id)
@ -256,17 +261,8 @@ class ChatAction(EventBuilder):
if isinstance(self._pinned_message, int)\ if isinstance(self._pinned_message, int)\
and await self.get_input_chat(): and await self.get_input_chat():
r = await self._client(functions.channels.GetMessagesRequest( self._pinned_message = await self._client.get_messages(
self._input_chat, [self._pinned_message] self._input_chat, ids=self._pinned_message)
))
try:
self._pinned_message = next(
x for x in r.messages
if isinstance(x, types.Message)
and x.id == self._pinned_message
)
except StopIteration:
pass
if isinstance(self._pinned_message, types.Message): if isinstance(self._pinned_message, types.Message):
return self._pinned_message return self._pinned_message
@ -316,7 +312,7 @@ class ChatAction(EventBuilder):
@property @property
def user(self): def user(self):
""" """
The first user that takes part in this action (e.g. joined). The first user that takes part in this action. For example, who joined.
Might be `None` if the information can't be retrieved or Might be `None` if the information can't be retrieved or
there is no user taking part. there is no user taking part.
@ -357,7 +353,7 @@ class ChatAction(EventBuilder):
@property @property
def users(self): def users(self):
""" """
A list of users that take part in this action (e.g. joined). A list of users that take part in this action. For example, who joined.
Might be empty if the information can't be retrieved or there Might be empty if the information can't be retrieved or there
are no users taking part. are no users taking part.
@ -381,7 +377,8 @@ class ChatAction(EventBuilder):
if not self._user_ids: if not self._user_ids:
return [] return []
if self._users is None or len(self._users) != len(self._user_ids): # Note: we access the property first so that it fills if needed
if (self.users is None or len(self._users) != len(self._user_ids)) and self.action_message:
await self.action_message._reload_message() await self.action_message._reload_message()
self._users = [ self._users = [
u for u in self.action_message.action_entities u for u in self.action_message.action_entities
@ -397,19 +394,31 @@ class ChatAction(EventBuilder):
if self._input_users is None and self._user_ids: if self._input_users is None and self._user_ids:
self._input_users = [] self._input_users = []
for user_id in self._user_ids: for user_id in self._user_ids:
# First try to get it from our entities
try:
self._input_users.append(utils.get_input_peer(self._entities[user_id]))
continue
except (KeyError, TypeError):
pass
# If missing, try from the entity cache
try: try:
self._input_users.append(self._client._entity_cache[user_id]) self._input_users.append(self._client._entity_cache[user_id])
continue
except KeyError: except KeyError:
pass pass
return self._input_users or [] return self._input_users or []
async def get_input_users(self): async def get_input_users(self):
""" """
Returns `input_users` but will make an API call if necessary. Returns `input_users` but will make an API call if necessary.
""" """
self._input_users = None if not self._user_ids:
if self._input_users is None: return []
await self.action_message._reload_message()
# Note: we access the property first so that it fills if needed
if (self.input_users is None or len(self._input_users) != len(self._user_ids)) and self.action_message:
self._input_users = [ self._input_users = [
utils.get_input_peer(u) utils.get_input_peer(u)
for u in self.action_message.action_entities for u in self.action_message.action_entities

View File

@ -1,10 +1,9 @@
import abc import abc
import asyncio import asyncio
import itertools
import warnings import warnings
from .. import utils from .. import utils
from ..tl import TLObject, types, functions from ..tl import TLObject, types
from ..tl.custom.chatgetter import ChatGetter from ..tl.custom.chatgetter import ChatGetter
@ -55,7 +54,7 @@ class EventBuilder(abc.ABC):
which will be ignored if ``blacklist_chats=True``. which will be ignored if ``blacklist_chats=True``.
func (`callable`, optional): func (`callable`, optional):
A callable function that should accept the event as input A callable (async or not) function that should accept the event as input
parameter, and return a value indicating whether the event parameter, and return a value indicating whether the event
should be dispatched or not (any truthy value will do, it should be dispatched or not (any truthy value will do, it
does not need to be a `bool`). It works like a custom filter: does not need to be a `bool`). It works like a custom filter:
@ -93,7 +92,7 @@ class EventBuilder(abc.ABC):
return return
if not self._resolve_lock: if not self._resolve_lock:
self._resolve_lock = asyncio.Lock(loop=client.loop) self._resolve_lock = asyncio.Lock()
async with self._resolve_lock: async with self._resolve_lock:
if not self.resolved: if not self.resolved:
@ -105,13 +104,13 @@ class EventBuilder(abc.ABC):
def filter(self, event): def filter(self, event):
""" """
If the ID of ``event._chat_peer`` isn't in the chats set (or it is Returns a truthy value if the event passed the filter and should be
but the set is a blacklist) returns `None`, otherwise the event. used, or falsy otherwise. The return value may need to be awaited.
The events must have been resolved before this can be called. The events must have been resolved before this can be called.
""" """
if not self.resolved: if not self.resolved:
return None return
if self.chats is not None: if self.chats is not None:
# Note: the `event.chat_id` property checks if it's `None` for us # Note: the `event.chat_id` property checks if it's `None` for us
@ -119,10 +118,13 @@ class EventBuilder(abc.ABC):
if inside == self.blacklist_chats: if inside == self.blacklist_chats:
# If this chat matches but it's a blacklist ignore. # If this chat matches but it's a blacklist ignore.
# If it doesn't match but it's a whitelist ignore. # If it doesn't match but it's a whitelist ignore.
return None return
if not self.func or self.func(event): if not self.func:
return event return True
# Return the result of func directly as it may need to be awaited
return self.func(event)
class EventCommon(ChatGetter, abc.ABC): class EventCommon(ChatGetter, abc.ABC):

View File

@ -79,11 +79,11 @@ class InlineQuery(EventBuilder):
Represents the event of a new callback query. Represents the event of a new callback query.
Members: Members:
query (:tl:`UpdateBotCallbackQuery`): query (:tl:`UpdateBotInlineQuery`):
The original :tl:`UpdateBotCallbackQuery`. The original :tl:`UpdateBotInlineQuery`.
Make sure to access the `text` of the query if Make sure to access the `text` property of the query if
that's what you want instead working with this. you want the text rather than the actual query object.
pattern_match (`obj`, optional): pattern_match (`obj`, optional):
The resulting object from calling the passed ``pattern`` The resulting object from calling the passed ``pattern``
@ -206,10 +206,9 @@ class InlineQuery(EventBuilder):
return return
if results: if results:
futures = [self._as_future(x, self._client.loop) futures = [self._as_future(x) for x in results]
for x in results]
await asyncio.wait(futures, loop=self._client.loop) await asyncio.wait(futures)
# All futures will be in the `done` *set* that `wait` returns. # All futures will be in the `done` *set* that `wait` returns.
# #
@ -236,10 +235,10 @@ class InlineQuery(EventBuilder):
) )
@staticmethod @staticmethod
def _as_future(obj, loop): def _as_future(obj):
if inspect.isawaitable(obj): if inspect.isawaitable(obj):
return asyncio.ensure_future(obj, loop=loop) return asyncio.ensure_future(obj)
f = loop.create_future() f = asyncio.get_event_loop().create_future()
f.set_result(obj) f.set_result(obj)
return f return f

View File

@ -1,4 +1,3 @@
import asyncio
import re import re
from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set from .common import EventBuilder, EventCommon, name_inner_event, _into_id_set

View File

@ -29,12 +29,12 @@ class Raw(EventBuilder):
self.types = None self.types = None
elif not utils.is_list_like(types): elif not utils.is_list_like(types):
if not isinstance(types, type): if not isinstance(types, type):
raise TypeError('Invalid input type given %s', types) raise TypeError('Invalid input type given: {}'.format(types))
self.types = types self.types = types
else: else:
if not all(isinstance(x, type) for x in types): if not all(isinstance(x, type) for x in types):
raise TypeError('Invalid input types given %s', types) raise TypeError('Invalid input types given: {}'.format(types))
self.types = tuple(types) self.types = tuple(types)
@ -46,6 +46,8 @@ class Raw(EventBuilder):
return update return update
def filter(self, event): def filter(self, event):
if ((not self.types or isinstance(event, self.types)) if not self.types or isinstance(event, self.types):
and (not self.func or self.func(event))): if self.func:
# Return the result of func directly as it may need to be awaited
return self.func(event)
return event return event

View File

@ -22,11 +22,10 @@ class MessagePacker:
point where outgoing requests are put, and where ready-messages are get. point where outgoing requests are put, and where ready-messages are get.
""" """
def __init__(self, state, loop, loggers): def __init__(self, state, loggers):
self._state = state self._state = state
self._loop = loop
self._deque = collections.deque() self._deque = collections.deque()
self._ready = asyncio.Event(loop=loop) self._ready = asyncio.Event()
self._log = loggers[__name__] self._log = loggers[__name__]
def append(self, state): def append(self, state):

View File

@ -3,6 +3,7 @@ import asyncio
import enum import enum
import os import os
import struct import struct
import inspect
from hashlib import sha1 from hashlib import sha1
@ -107,6 +108,13 @@ def retry_range(retries):
yield 1 + attempt yield 1 + attempt
async def _maybe_await(value):
if inspect.isawaitable(value):
return await value
else:
return value
async def _cancel(log, **tasks): async def _cancel(log, **tasks):
""" """
Helper to cancel one or more tasks gracefully, logging exceptions. Helper to cancel one or more tasks gracefully, logging exceptions.
@ -123,6 +131,8 @@ async def _cancel(log, **tasks):
except RuntimeError: except RuntimeError:
# Probably: RuntimeError: await wasn't used with future # 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) # Happens with _asyncio.Task instances (in "Task cancelling" state)
# trying to SIGINT the program right during initial connection, on # trying to SIGINT the program right during initial connection, on
# _recv_loop coroutine (but we're creating its task explicitly with # _recv_loop coroutine (but we're creating its task explicitly with
@ -131,6 +141,12 @@ async def _cancel(log, **tasks):
# Since we're aware of this error there's no point in logging it. # Since we're aware of this error there's no point in logging it.
# *May* be https://bugs.python.org/issue37172 # *May* be https://bugs.python.org/issue37172
pass 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: except Exception:
log.exception('Unhandled exception from %s after cancelling ' log.exception('Unhandled exception from %s after cancelling '
'%s (%s)', name, type(task), task) '%s (%s)', name, type(task), task)

View File

@ -28,11 +28,10 @@ class Connection(abc.ABC):
# should be one of `PacketCodec` implementations # should be one of `PacketCodec` implementations
packet_codec = None packet_codec = None
def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): def __init__(self, ip, port, dc_id, *, loggers, proxy=None):
self._ip = ip self._ip = ip
self._port = port self._port = port
self._dc_id = dc_id # only for MTProxy, it's an abstraction leak self._dc_id = dc_id # only for MTProxy, it's an abstraction leak
self._loop = loop
self._log = loggers[__name__] self._log = loggers[__name__]
self._proxy = proxy self._proxy = proxy
self._reader = None self._reader = None
@ -48,9 +47,8 @@ class Connection(abc.ABC):
async def _connect(self, timeout=None, ssl=None): async def _connect(self, timeout=None, ssl=None):
if not self._proxy: if not self._proxy:
self._reader, self._writer = await asyncio.wait_for( self._reader, self._writer = await asyncio.wait_for(
asyncio.open_connection( asyncio.open_connection(self._ip, self._port, ssl=ssl),
self._ip, self._port, loop=self._loop, ssl=ssl), timeout=timeout
loop=self._loop, timeout=timeout
) )
else: else:
import socks import socks
@ -65,11 +63,10 @@ class Connection(abc.ABC):
else: else:
s.set_proxy(*self._proxy) s.set_proxy(*self._proxy)
s.setblocking(False) s.settimeout(timeout)
await asyncio.wait_for( await asyncio.wait_for(
self._loop.sock_connect(s, address), asyncio.get_event_loop().sock_connect(s, address),
timeout=timeout, timeout=timeout
loop=self._loop
) )
if ssl: if ssl:
if ssl_mod is None: if ssl_mod is None:
@ -78,17 +75,16 @@ class Connection(abc.ABC):
'without the SSL module being available' 'without the SSL module being available'
) )
s.settimeout(timeout)
s = ssl_mod.wrap_socket( s = ssl_mod.wrap_socket(
s, s,
do_handshake_on_connect=True, do_handshake_on_connect=True,
ssl_version=ssl_mod.PROTOCOL_SSLv23, ssl_version=ssl_mod.PROTOCOL_SSLv23,
ciphers='ADH-AES256-SHA' ciphers='ADH-AES256-SHA'
) )
s.setblocking(False)
s.setblocking(False)
self._reader, self._writer = \ self._reader, self._writer = await asyncio.open_connection(sock=s)
await asyncio.open_connection(sock=s, loop=self._loop)
self._codec = self.packet_codec(self) self._codec = self.packet_codec(self)
self._init_conn() self._init_conn()
@ -101,8 +97,9 @@ class Connection(abc.ABC):
await self._connect(timeout=timeout, ssl=ssl) await self._connect(timeout=timeout, ssl=ssl)
self._connected = True self._connected = True
self._send_task = self._loop.create_task(self._send_loop()) loop = asyncio.get_event_loop()
self._recv_task = self._loop.create_task(self._recv_loop()) self._send_task = loop.create_task(self._send_loop())
self._recv_task = loop.create_task(self._recv_loop())
async def disconnect(self): async def disconnect(self):
""" """

View File

@ -95,12 +95,12 @@ class TcpMTProxy(ObfuscatedConnection):
obfuscated_io = MTProxyIO obfuscated_io = MTProxyIO
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def __init__(self, ip, port, dc_id, *, loop, loggers, proxy=None): def __init__(self, ip, port, dc_id, *, loggers, proxy=None):
# connect to proxy's host and port instead of telegram's ones # connect to proxy's host and port instead of telegram's ones
proxy_host, proxy_port = self.address_info(proxy) proxy_host, proxy_port = self.address_info(proxy)
self._secret = bytes.fromhex(proxy[2]) self._secret = bytes.fromhex(proxy[2])
super().__init__( super().__init__(
proxy_host, proxy_port, dc_id, loop=loop, loggers=loggers) proxy_host, proxy_port, dc_id, loggers=loggers)
async def _connect(self, timeout=None, ssl=None): async def _connect(self, timeout=None, ssl=None):
await super()._connect(timeout=timeout, ssl=ssl) await super()._connect(timeout=timeout, ssl=ssl)

View File

@ -40,12 +40,11 @@ class MTProtoSender:
A new authorization key will be generated on connection if no other A new authorization key will be generated on connection if no other
key exists yet. key exists yet.
""" """
def __init__(self, auth_key, loop, *, loggers, def __init__(self, auth_key, *, loggers,
retries=5, delay=1, auto_reconnect=True, connect_timeout=None, retries=5, delay=1, auto_reconnect=True, connect_timeout=None,
auth_key_callback=None, auth_key_callback=None,
update_callback=None, auto_reconnect_callback=None): update_callback=None, auto_reconnect_callback=None):
self._connection = None self._connection = None
self._loop = loop
self._loggers = loggers self._loggers = loggers
self._log = loggers[__name__] self._log = loggers[__name__]
self._retries = retries self._retries = retries
@ -55,7 +54,7 @@ class MTProtoSender:
self._auth_key_callback = auth_key_callback self._auth_key_callback = auth_key_callback
self._update_callback = update_callback self._update_callback = update_callback
self._auto_reconnect_callback = auto_reconnect_callback self._auto_reconnect_callback = auto_reconnect_callback
self._connect_lock = asyncio.Lock(loop=loop) self._connect_lock = asyncio.Lock()
# Whether the user has explicitly connected or disconnected. # Whether the user has explicitly connected or disconnected.
# #
@ -65,7 +64,7 @@ class MTProtoSender:
# pending futures should be cancelled. # pending futures should be cancelled.
self._user_connected = False self._user_connected = False
self._reconnecting = False self._reconnecting = False
self._disconnected = self._loop.create_future() self._disconnected = asyncio.get_event_loop().create_future()
self._disconnected.set_result(None) self._disconnected.set_result(None)
# We need to join the loops upon disconnection # We need to join the loops upon disconnection
@ -78,8 +77,7 @@ class MTProtoSender:
# Outgoing messages are put in a queue and sent in a batch. # Outgoing messages are put in a queue and sent in a batch.
# Note that here we're also storing their ``_RequestState``. # Note that here we're also storing their ``_RequestState``.
self._send_queue = MessagePacker(self._state, self._loop, self._send_queue = MessagePacker(self._state, loggers=self._loggers)
loggers=self._loggers)
# Sent states are remembered until a response is received. # Sent states are remembered until a response is received.
self._pending_state = {} self._pending_state = {}
@ -171,7 +169,7 @@ class MTProtoSender:
if not utils.is_list_like(request): if not utils.is_list_like(request):
try: try:
state = RequestState(request, self._loop) state = RequestState(request)
except struct.error as e: except struct.error as e:
# "struct.error: required argument is not an integer" is not # "struct.error: required argument is not an integer" is not
# very helpful; log the request to find out what wasn't int. # very helpful; log the request to find out what wasn't int.
@ -186,7 +184,7 @@ class MTProtoSender:
state = None state = None
for req in request: for req in request:
try: try:
state = RequestState(req, self._loop, after=ordered and state) state = RequestState(req, after=ordered and state)
except struct.error as e: except struct.error as e:
self._log.error('Request caused struct.error: %s: %s', e, request) self._log.error('Request caused struct.error: %s: %s', e, request)
raise raise
@ -206,7 +204,7 @@ class MTProtoSender:
Note that it may resolve in either a ``ConnectionError`` Note that it may resolve in either a ``ConnectionError``
or any other unexpected error that could not be handled. or any other unexpected error that could not be handled.
""" """
return asyncio.shield(self._disconnected, loop=self._loop) return asyncio.shield(self._disconnected)
# Private methods # Private methods
@ -241,7 +239,7 @@ class MTProtoSender:
# reconnect cleanly after. # reconnect cleanly after.
await self._connection.disconnect() await self._connection.disconnect()
connected = False connected = False
await asyncio.sleep(self._delay, loop=self._loop) await asyncio.sleep(self._delay)
continue # next iteration we will try to reconnect continue # next iteration we will try to reconnect
break # all steps done, break retry loop break # all steps done, break retry loop
@ -253,17 +251,18 @@ class MTProtoSender:
await self._disconnect(error=e) await self._disconnect(error=e)
raise e raise e
loop = asyncio.get_event_loop()
self._log.debug('Starting send loop') self._log.debug('Starting send loop')
self._send_loop_handle = self._loop.create_task(self._send_loop()) self._send_loop_handle = loop.create_task(self._send_loop())
self._log.debug('Starting receive loop') self._log.debug('Starting receive loop')
self._recv_loop_handle = self._loop.create_task(self._recv_loop()) self._recv_loop_handle = loop.create_task(self._recv_loop())
# _disconnected only completes after manual disconnection # _disconnected only completes after manual disconnection
# or errors after which the sender cannot continue such # or errors after which the sender cannot continue such
# as failing to reconnect or any unexpected error. # as failing to reconnect or any unexpected error.
if self._disconnected.done(): if self._disconnected.done():
self._disconnected = self._loop.create_future() self._disconnected = loop.create_future()
self._log.info('Connection to %s complete!', self._connection) self._log.info('Connection to %s complete!', self._connection)
@ -378,7 +377,7 @@ class MTProtoSender:
self._pending_state.clear() self._pending_state.clear()
if self._auto_reconnect_callback: if self._auto_reconnect_callback:
self._loop.create_task(self._auto_reconnect_callback()) asyncio.get_event_loop().create_task(self._auto_reconnect_callback())
break break
else: else:
@ -398,7 +397,7 @@ class MTProtoSender:
# gets stuck. # gets stuck.
# TODO It still gets stuck? Investigate where and why. # TODO It still gets stuck? Investigate where and why.
self._reconnecting = True self._reconnecting = True
self._loop.create_task(self._reconnect(error)) asyncio.get_event_loop().create_task(self._reconnect(error))
# Loops # Loops
@ -411,7 +410,7 @@ class MTProtoSender:
""" """
while self._user_connected and not self._reconnecting: while self._user_connected and not self._reconnecting:
if self._pending_ack: if self._pending_ack:
ack = RequestState(MsgsAck(list(self._pending_ack)), self._loop) ack = RequestState(MsgsAck(list(self._pending_ack)))
self._send_queue.append(ack) self._send_queue.append(ack)
self._last_acks.append(ack) self._last_acks.append(ack)
self._pending_ack.clear() self._pending_ack.clear()
@ -564,7 +563,7 @@ class MTProtoSender:
if rpc_result.error: if rpc_result.error:
error = rpc_message_to_error(rpc_result.error, state.request) error = rpc_message_to_error(rpc_result.error, state.request)
self._send_queue.append( self._send_queue.append(
RequestState(MsgsAck([state.msg_id]), loop=self._loop)) RequestState(MsgsAck([state.msg_id])))
if not state.future.cancelled(): if not state.future.cancelled():
state.future.set_exception(error) state.future.set_exception(error)
@ -751,8 +750,8 @@ class MTProtoSender:
enqueuing a :tl:`MsgsStateInfo` to be sent at a later point. enqueuing a :tl:`MsgsStateInfo` to be sent at a later point.
""" """
self._send_queue.append(RequestState(MsgsStateInfo( self._send_queue.append(RequestState(MsgsStateInfo(
req_msg_id=message.msg_id, info=chr(1) * len(message.obj.msg_ids)), req_msg_id=message.msg_id, info=chr(1) * len(message.obj.msg_ids)
loop=self._loop)) )))
async def _handle_msg_all(self, message): async def _handle_msg_all(self, message):
""" """

View File

@ -10,10 +10,10 @@ class RequestState:
""" """
__slots__ = ('container_id', 'msg_id', 'request', 'data', 'future', 'after') __slots__ = ('container_id', 'msg_id', 'request', 'data', 'future', 'after')
def __init__(self, request, loop, after=None): def __init__(self, request, after=None):
self.container_id = None self.container_id = None
self.msg_id = None self.msg_id = None
self.request = request self.request = request
self.data = bytes(request) self.data = bytes(request)
self.future = asyncio.Future(loop=loop) self.future = asyncio.Future()
self.after = after self.after = after

View File

@ -162,7 +162,6 @@ def compute_check(request: types.account.Password, password: str):
def generate_and_check_random(): def generate_and_check_random():
random_size = 256 random_size = 256
import time
while True: while True:
random = os.urandom(random_size) random = os.urandom(random_size)
a = int.from_bytes(random, 'big') a = int.from_bytes(random, 'big')

View File

@ -65,8 +65,7 @@ class RequestIter(abc.ABC):
# asyncio will handle times <= 0 to sleep 0 seconds # asyncio will handle times <= 0 to sleep 0 seconds
if self.wait_time: if self.wait_time:
await asyncio.sleep( await asyncio.sleep(
self.wait_time - (time.time() - self.last_load), self.wait_time - (time.time() - self.last_load)
loop=self.client.loop
) )
self.last_load = time.time() self.last_load = time.time()

View File

@ -1,4 +1,3 @@
import datetime
import inspect import inspect
from .tl import types from .tl import types

View File

@ -1,7 +1,7 @@
import gzip import gzip
import struct import struct
from .. import TLObject, TLRequest from .. import TLObject
class GzipPacked(TLObject): class GzipPacked(TLObject):

View File

@ -10,3 +10,4 @@ from .inlinebuilder import InlineBuilder
from .inlineresult import InlineResult from .inlineresult import InlineResult
from .inlineresults import InlineResults from .inlineresults import InlineResults
from .conversation import Conversation from .conversation import Conversation
from .qrlogin import QRLogin

View File

@ -1,4 +1,6 @@
import asyncio import asyncio
import functools
import inspect
import itertools import itertools
import time import time
@ -11,6 +13,16 @@ from ... import helpers, utils, errors
_EDIT_COLLISION_DELTA = 0.001 _EDIT_COLLISION_DELTA = 0.001
def _checks_cancelled(f):
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if self._cancelled:
raise asyncio.CancelledError('The conversation was cancelled before')
return f(self, *args, **kwargs)
return wrapper
class Conversation(ChatGetter): class Conversation(ChatGetter):
""" """
Represents a conversation inside an specific chat. Represents a conversation inside an specific chat.
@ -66,6 +78,7 @@ class Conversation(ChatGetter):
self._edit_dates = {} self._edit_dates = {}
@_checks_cancelled
async def send_message(self, *args, **kwargs): async def send_message(self, *args, **kwargs):
""" """
Sends a message in the context of this conversation. Shorthand Sends a message in the context of this conversation. Shorthand
@ -81,6 +94,7 @@ class Conversation(ChatGetter):
self._last_outgoing = ms[-1].id self._last_outgoing = ms[-1].id
return sent return sent
@_checks_cancelled
async def send_file(self, *args, **kwargs): async def send_file(self, *args, **kwargs):
""" """
Sends a file in the context of this conversation. Shorthand Sends a file in the context of this conversation. Shorthand
@ -96,6 +110,7 @@ class Conversation(ChatGetter):
self._last_outgoing = ms[-1].id self._last_outgoing = ms[-1].id
return sent return sent
@_checks_cancelled
def mark_read(self, message=None): def mark_read(self, message=None):
""" """
Marks as read the latest received message if ``message is None``. Marks as read the latest received message if ``message is None``.
@ -117,7 +132,8 @@ class Conversation(ChatGetter):
def get_response(self, message=None, *, timeout=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: Args:
message (`Message <telethon.tl.custom.message.Message>` | `int`, optional): message (`Message <telethon.tl.custom.message.Message>` | `int`, optional):
@ -127,6 +143,16 @@ class Conversation(ChatGetter):
timeout (`int` | `float`, optional): timeout (`int` | `float`, optional):
If present, this `timeout` (in seconds) will override the If present, this `timeout` (in seconds) will override the
per-action timeout defined for the conversation. 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( return self._get_message(
message, self._response_indices, self._pending_responses, timeout, message, self._response_indices, self._pending_responses, timeout,
@ -257,23 +283,41 @@ class Conversation(ChatGetter):
.. note:: .. 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, For example, don't use `wait_event` for new messages,
since `get_response` already exists, etc. since `get_response` already exists, etc.
Unless you're certain that your code will run fast enough, Unless you're certain that your code will run fast enough,
generally you should get a "handle" of this special coroutine generally you should get a "handle" of this special coroutine
before acting. Generally, you should do this: before acting. In this example you will see how to wait for a user
to join a group with proper use of `wait_event`:
>>> from telethon import TelegramClient, events .. code-block:: python
>>>
>>> client = TelegramClient(...) from telethon import TelegramClient, events
>>>
>>> async def main(): client = TelegramClient(...)
>>> async with client.conversation(...) as conv: group_id = ...
>>> response = conv.wait_event(events.NewMessage(incoming=True))
>>> await conv.send_message('Hi') async def main():
>>> response = await response # 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, This way your event can be registered before acting,
since the response may arrive before your event was since the response may arrive before your event was
@ -298,9 +342,15 @@ class Conversation(ChatGetter):
for key, (ev, fut) in list(self._custom.items()): for key, (ev, fut) in list(self._custom.items()):
ev_type = type(ev) ev_type = type(ev)
inst = built[ev_type] inst = built[ev_type]
if inst and ev.filter(inst):
fut.set_result(inst) if inst:
del self._custom[key] filter = ev.filter(inst)
if inspect.isawaitable(filter):
filter = await filter
if filter:
fut.set_result(inst)
del self._custom[key]
def _on_new_message(self, response): def _on_new_message(self, response):
response = response.message response = response.message
@ -379,10 +429,8 @@ class Conversation(ChatGetter):
else: else:
raise ValueError('No message was sent previously') raise ValueError('No message was sent previously')
@_checks_cancelled
def _get_result(self, future, start_time, timeout, pending, target_id): def _get_result(self, future, start_time, timeout, pending, target_id):
if self._cancelled:
raise asyncio.CancelledError('The conversation was cancelled before')
due = self._total_due due = self._total_due
if timeout is None: if timeout is None:
timeout = self._timeout timeout = self._timeout
@ -397,8 +445,7 @@ class Conversation(ChatGetter):
# cleared when their futures are set to a result. # cleared when their futures are set to a result.
return asyncio.wait_for( return asyncio.wait_for(
future, future,
timeout=None if due == float('inf') else due - time.time(), timeout=None if due == float('inf') else due - time.time()
loop=self._client.loop
) )
def _cancel_all(self, exception=None): def _cancel_all(self, exception=None):

View File

@ -552,6 +552,14 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
if isinstance(self.media, types.MessageMediaVenue): if isinstance(self.media, types.MessageMediaVenue):
return self.media return self.media
@property
def dice(self):
"""
The :tl:`MessageMediaDice` in this message, if it's a dice roll.
"""
if isinstance(self.media, types.MessageMediaDice):
return self.media
@property @property
def action_entities(self): def action_entities(self):
""" """
@ -756,7 +764,8 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
return await self._client.download_media(self, *args, **kwargs) return await self._client.download_media(self, *args, **kwargs)
async def click(self, i=None, j=None, 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>` Calls `button.click <telethon.tl.custom.messagebutton.MessageButton.click>`
on the specified button. on the specified button.
@ -805,6 +814,28 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
that if the message does not have this data, it will that if the message does not have this data, it will
``raise DataInvalidError``. ``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: Example:
.. code-block:: python .. code-block:: python
@ -820,6 +851,9 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
# Click by data # Click by data
await message.click(data=b'payload') await message.click(data=b'payload')
# Click on a button requesting a phone
await message.click(0, share_phone=True)
""" """
if not self._client: if not self._client:
return return
@ -836,7 +870,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
data=data data=data
) )
) )
except errors.BotTimeout: except errors.BotResponseTimeoutError:
return None return None
if sum(int(x is not None) for x in (i, text, filter)) >= 2: if sum(int(x is not None) for x in (i, text, filter)) >= 2:
@ -845,29 +879,35 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
if not await self.get_buttons(): if not await self.get_buttons():
return # Accessing the property sets self._buttons[_flat] return # Accessing the property sets self._buttons[_flat]
if text is not None: def find_button():
if callable(text): 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: for button in self._buttons_flat:
if text(button.text): if filter(button):
return await button.click() return button
return
if i is None:
i = 0
if j is None:
return self._buttons_flat[i]
else: else:
for button in self._buttons_flat: return self._buttons[i][j]
if button.text == text:
return await button.click()
return
if filter is not None: button = find_button()
for button in self._buttons_flat: if button:
if filter(button): return await button.click(share_phone=share_phone, share_geo=share_geo)
return await button.click()
return
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()
async def mark_read(self): async def mark_read(self):
""" """
@ -890,7 +930,7 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
# maybe just make it illegal to call messages from raw API? # maybe just make it illegal to call messages from raw API?
# That or figure out a way to always set it directly. # That or figure out a way to always set it directly.
if self._client: if self._client:
await self._client.pin_message( return await self._client.pin_message(
await self.get_input_chat(), self.id, notify=notify) await self.get_input_chat(), self.id, notify=notify)
# endregion Public Methods # endregion Public Methods

View File

@ -1,5 +1,5 @@
from .. import types, functions from .. import types, functions
from ...errors import BotTimeout from ...errors import BotResponseTimeoutError
import webbrowser import webbrowser
@ -59,7 +59,7 @@ class MessageButton:
if isinstance(self.button, types.KeyboardButtonUrl): if isinstance(self.button, types.KeyboardButtonUrl):
return self.button.url 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. Emulates the behaviour of clicking this button.
@ -75,6 +75,19 @@ class MessageButton:
If it's a :tl:`KeyboardButtonUrl`, the URL of the button will If it's a :tl:`KeyboardButtonUrl`, the URL of the button will
be passed to ``webbrowser.open`` and return `True` on success. 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): if isinstance(self.button, types.KeyboardButton):
return await self._client.send_message( return await self._client.send_message(
@ -85,7 +98,7 @@ class MessageButton:
) )
try: try:
return await self._client(req) return await self._client(req)
except BotTimeout: except BotResponseTimeoutError:
return None return None
elif isinstance(self.button, types.KeyboardButtonSwitchInline): elif isinstance(self.button, types.KeyboardButtonSwitchInline):
return await self._client(functions.messages.StartBotRequest( return await self._client(functions.messages.StartBotRequest(
@ -99,5 +112,28 @@ class MessageButton:
) )
try: try:
return await self._client(req) return await self._client(req)
except BotTimeout: except BotResponseTimeoutError:
return None 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)

View File

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

View File

@ -186,6 +186,19 @@ class TLObject:
return json.dumps(d, default=default, **kwargs) return json.dumps(d, default=default, **kwargs)
def __bytes__(self): def __bytes__(self):
try:
return self._bytes()
except AttributeError:
# If a type is wrong (e.g. expected `TLObject` but `int` was
# provided) it will try to access `._bytes()`, which will fail
# with `AttributeError`. This occurs in fact because the type
# was wrong, so raise the correct error type.
raise TypeError('a TLObject was expected but found something else')
# Custom objects will call `(...)._bytes()` and not `bytes(...)` so that
# if the wrong type is used (e.g. `int`) we won't try allocating a huge
# amount of data, which would cause a `MemoryError`.
def _bytes(self):
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod

View File

@ -51,6 +51,8 @@ mimetypes.add_type('audio/aac', '.aac')
mimetypes.add_type('audio/ogg', '.ogg') mimetypes.add_type('audio/ogg', '.ogg')
mimetypes.add_type('audio/flac', '.flac') mimetypes.add_type('audio/flac', '.flac')
mimetypes.add_type('application/x-tgsticker', '.tgs')
USERNAME_RE = re.compile( USERNAME_RE = re.compile(
r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(@|joinchat/)?' r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(@|joinchat/)?'
) )
@ -64,7 +66,7 @@ TG_JOIN_RE = re.compile(
# #
# See https://telegram.org/blog/inline-bots#how-does-it-work # See https://telegram.org/blog/inline-bots#how-does-it-work
VALID_USERNAME_RE = re.compile( VALID_USERNAME_RE = re.compile(
r'^([a-z]((?!__)[\w\d]){3,30}[a-z\d]' r'^([a-z](?:(?!__)\w){3,30}[a-z\d]'
r'|gif|vid|pic|bing|wiki|imdb|bold|vote|like|coub)$', r'|gif|vid|pic|bing|wiki|imdb|bold|vote|like|coub)$',
re.IGNORECASE re.IGNORECASE
) )
@ -481,7 +483,7 @@ def get_input_media(
supports_streaming=supports_streaming supports_streaming=supports_streaming
) )
return types.InputMediaUploadedDocument( return types.InputMediaUploadedDocument(
file=media, mime_type=mime, attributes=attrs) file=media, mime_type=mime, attributes=attrs, force_file=force_document)
if isinstance(media, types.MessageMediaGame): if isinstance(media, types.MessageMediaGame):
return types.InputMediaGame(id=types.InputGameID( return types.InputMediaGame(id=types.InputGameID(
@ -510,6 +512,9 @@ def get_input_media(
venue_type='' venue_type=''
) )
if isinstance(media, types.MessageMediaDice):
return types.InputMediaDice(media.emoticon)
if isinstance(media, ( if isinstance(media, (
types.MessageMediaEmpty, types.MessageMediaUnsupported, types.MessageMediaEmpty, types.MessageMediaUnsupported,
types.ChatPhotoEmpty, types.UserProfilePhotoEmpty, types.ChatPhotoEmpty, types.UserProfilePhotoEmpty,
@ -520,6 +525,27 @@ def get_input_media(
if isinstance(media, types.Message): if isinstance(media, types.Message):
return get_input_media(media.media, is_photo=is_photo) return get_input_media(media.media, is_photo=is_photo)
if isinstance(media, types.MessageMediaPoll):
if media.poll.quiz:
if not media.results.results:
# A quiz has correct answers, which we don't know until answered.
# If the quiz hasn't been answered we can't reconstruct it properly.
raise TypeError('Cannot cast unanswered quiz to any kind of InputMedia.')
correct_answers = [r.option for r in media.results.results if r.correct]
else:
correct_answers = None
return types.InputMediaPoll(
poll=media.poll,
correct_answers=correct_answers,
solution=media.results.solution,
solution_entities=media.results.solution_entities,
)
if isinstance(media, types.Poll):
return types.InputMediaPoll(media)
_raise_cast_fail(media, 'InputMedia') _raise_cast_fail(media, 'InputMedia')
@ -1230,7 +1256,7 @@ def get_appropriated_part_size(file_size):
return 128 return 128
if file_size <= 786432000: # 750MB if file_size <= 786432000: # 750MB
return 256 return 256
if file_size <= 1572864000: # 1500MB if file_size <= 2097152000: # 2000MB
return 512 return 512
raise ValueError('File size too large') raise ValueError('File size too large')

View File

@ -1,3 +1,3 @@
# Versions should comply with PEP440. # Versions should comply with PEP440.
# This line is parsed in setup.py: # This line is parsed in setup.py:
__version__ = '1.11.2' __version__ = '1.16.2'

View File

@ -130,6 +130,18 @@ assumes some [`asyncio`] knowledge, but otherwise is easy to follow.
![Screenshot of the tkinter GUI][tkinter GUI] ![Screenshot of the tkinter GUI][tkinter GUI]
### [`payment.py`](https://raw.githubusercontent.com/LonamiWebs/Telethon/master/telethon_examples/payment.py)
* Usable as: **bot**.
* Difficulty: **medium**.
This example shows how to make invoices (Telegram's way of requesting payments) via a bot account. The example does not include how to add shipping information, though.
You'll need to obtain a "provider token" to use this example, so please read [Telegram's guide on payments](https://core.telegram.org/bots/payments) before using this example.
It makes use of the ["raw API"](https://tl.telethon.dev) (that is, no friendly `client.` methods), which can be helpful in understanding how it works and how it can be used.
[Telethon]: https://github.com/LonamiWebs/Telethon [Telethon]: https://github.com/LonamiWebs/Telethon
[CC0 License]: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/LICENSE [CC0 License]: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/LICENSE

View File

@ -341,8 +341,8 @@ class App(tkinter.Tk):
self.chat.configure(bg='yellow') self.chat.configure(bg='yellow')
async def main(loop, interval=0.05): async def main(interval=0.05):
client = TelegramClient(SESSION, API_ID, API_HASH, loop=loop) client = TelegramClient(SESSION, API_ID, API_HASH)
try: try:
await client.connect() await client.connect()
except Exception as e: except Exception as e:
@ -372,7 +372,7 @@ if __name__ == "__main__":
# Some boilerplate code to set up the main method # Some boilerplate code to set up the main method
aio_loop = asyncio.get_event_loop() aio_loop = asyncio.get_event_loop()
try: try:
aio_loop.run_until_complete(main(aio_loop)) aio_loop.run_until_complete(main())
finally: finally:
if not aio_loop.is_closed(): if not aio_loop.is_closed():
aio_loop.close() aio_loop.close()

View File

@ -0,0 +1,183 @@
from telethon import TelegramClient, events, types, functions
import asyncio
import logging
import tracemalloc
import os
import time
import sys
loop = asyncio.get_event_loop()
"""
Provider token can be obtained via @BotFather. more info at https://core.telegram.org/bots/payments#getting-a-token
If you are using test token, set test=True in generate_invoice function,
If you are using real token, set test=False
"""
provider_token = ''
tracemalloc.start()
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.WARNING)
logger = logging.getLogger(__name__)
def get_env(name, message, cast=str):
if name in os.environ:
return os.environ[name]
while True:
value = input(message)
try:
return cast(value)
except ValueError as e:
print(e, file=sys.stderr)
time.sleep(1)
bot = TelegramClient(
os.environ.get('TG_SESSION', 'payment'),
get_env('TG_API_ID', 'Enter your API ID: ', int),
get_env('TG_API_HASH', 'Enter your API hash: '),
proxy=None
)
# That event is handled when customer enters his card/etc, on final pre-checkout
# If we don't `SetBotPrecheckoutResultsRequest`, money won't be charged from buyer, and nothing will happen next.
@bot.on(events.Raw(types.UpdateBotPrecheckoutQuery))
async def payment_pre_checkout_handler(event: types.UpdateBotPrecheckoutQuery):
if event.payload.decode('UTF-8') == 'product A':
# so we have to confirm payment
await bot(
functions.messages.SetBotPrecheckoutResultsRequest(
query_id=event.query_id,
success=True,
error=None
)
)
elif event.payload.decode('UTF-8') == 'product B':
# same for another
await bot(
functions.messages.SetBotPrecheckoutResultsRequest(
query_id=event.query_id,
success=True,
error=None
)
)
else:
# for example, something went wrong (whatever reason). We can tell customer about that:
await bot(
functions.messages.SetBotPrecheckoutResultsRequest(
query_id=event.query_id,
success=False,
error='Something went wrong'
)
)
raise events.StopPropagation
# That event is handled at the end, when customer payed.
@bot.on(events.Raw(types.UpdateNewMessage))
async def payment_received_handler(event):
if isinstance(event.message.action, types.MessageActionPaymentSentMe):
payment: types.MessageActionPaymentSentMe = event.message.action
# do something after payment was recieved
if payment.payload.decode('UTF-8') == 'product A':
await bot.send_message(event.message.from_id, 'Thank you for buying product A!')
elif payment.payload.decode('UTF-8') == 'product B':
await bot.send_message(event.message.from_id, 'Thank you for buying product B!')
raise events.StopPropagation
# let's put it in one function for more easier way
def generate_invoice(price_label: str, price_amount: int, currency: str, title: str,
description: str, payload: str, start_param: str) -> types.InputMediaInvoice:
price = types.LabeledPrice(label=price_label, amount=price_amount) # label - just a text, amount=10000 means 100.00
invoice = types.Invoice(
currency=currency, # currency like USD
prices=[price], # there could be a couple of prices.
test=True, # if you're working with test token, else set test=False.
# More info at https://core.telegram.org/bots/payments
# params for requesting specific fields
name_requested=False,
phone_requested=False,
email_requested=False,
shipping_address_requested=False,
# if price changes depending on shipping
flexible=False,
# send data to provider
phone_to_provider=False,
email_to_provider=False
)
return types.InputMediaInvoice(
title=title,
description=description,
invoice=invoice,
payload=payload.encode('UTF-8'), # payload, which will be sent to next 2 handlers
provider=provider_token,
provider_data=types.DataJSON('{}'),
# data about the invoice, which will be shared with the payment provider. A detailed description of
# required fields should be provided by the payment provider.
start_param=start_param,
# Unique deep-linking parameter. May also be used in UpdateBotPrecheckoutQuery
# see: https://core.telegram.org/bots#deep-linking
# it may be the empty string if not needed
)
@bot.on(events.NewMessage(pattern='/start'))
async def start_handler(event: events.NewMessage.Event):
await event.respond('/product_a - product A\n/product_b - product B\n/product_c - product, shall cause an error')
@bot.on(events.NewMessage(pattern='/product_a'))
async def start_handler(event: events.NewMessage.Event):
await bot.send_message(
event.chat_id, 'Sending invoice A',
file=generate_invoice(
price_label='Pay', price_amount=10000, currency='RUB', title='Title A', description='description A',
payload='product A', start_param='abc'
)
)
@bot.on(events.NewMessage(pattern='/product_b'))
async def start_handler(event: events.NewMessage.Event):
await bot.send_message(
event.chat_id, 'Sending invoice B',
file=generate_invoice(
price_label='Pay', price_amount=20000, currency='RUB', title='Title B', description='description B',
payload='product B', start_param='abc'
)
)
@bot.on(events.NewMessage(pattern='/product_c'))
async def start_handler(event: events.NewMessage.Event):
await bot.send_message(
event.chat_id, 'Sending invoice C',
file=generate_invoice(
price_label='Pay', price_amount=50000, currency='RUB', title='Title C',
description='description c - shall cause an error', payload='product C', start_param='abc'
)
)
async def main():
await bot.start()
await bot.run_until_disconnected()
if __name__ == '__main__':
if not provider_token:
logger.error("No provider token supplied.")
exit(1)
loop.run_until_complete(main())

View File

@ -62,19 +62,19 @@ inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<
inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia; inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector<bytes> solution:flags.1?string solution_entities:flags.1?Vector<MessageEntity> = InputMedia;
inputMediaDice#e66fbf7b emoticon:string = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto;
inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
inputGeoPointEmpty#e4c123d6 = InputGeoPoint; inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
@ -109,10 +109,10 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType; storage.fileWebp#1081464c = storage.FileType;
userEmpty#200250ba id:int = User; userEmpty#200250ba id:int = User;
user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector<RestrictionReason> bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto;
userStatusEmpty#9d05049 = UserStatus; userStatusEmpty#9d05049 = UserStatus;
userStatusOnline#edb93949 expires:int = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus;
@ -128,7 +128,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull;
channelFull#2d895c74 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int pts:int = ChatFull; channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@ -138,7 +138,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?
chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants; chatParticipants#3f460fed chat_id:int participants:Vector<ChatParticipant> version:int = ChatParticipants;
chatPhotoEmpty#37c1011c = ChatPhoto; chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto;
messageEmpty#83e5de54 id:int = Message; messageEmpty#83e5de54 id:int = Message;
message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message; message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
@ -156,6 +156,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction; messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
@ -185,7 +186,7 @@ dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer t
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
photoEmpty#2331b22d id:long = Photo; photoEmpty#2331b22d id:long = Photo;
photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> dc_id:int = Photo; photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> video_sizes:flags.1?Vector<VideoSize> dc_id:int = Photo;
photoSizeEmpty#e17e23c type:string = PhotoSize; photoSizeEmpty#e17e23c type:string = PhotoSize;
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
@ -211,7 +212,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings; peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings;
wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper;
wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper;
@ -224,7 +225,7 @@ inputReportReasonOther#e1746d0a text:string = ReportReason;
inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason;
inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact; contact#f911c994 user_id:int mutual:Bool = Contact;
@ -352,6 +353,11 @@ updateTheme#8216fba3 theme:Theme = Update;
updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update;
updateLoginToken#564fe691 = Update; updateLoginToken#564fe691 = Update;
updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update; updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector<bytes> = Update;
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;
updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -389,7 +395,7 @@ help.inviteText#18cb9f78 message:string = help.InviteText;
encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat;
encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat;
encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat;
encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat;
encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat;
@ -416,7 +422,7 @@ inputDocumentEmpty#72f0eaae = InputDocument;
inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
documentEmpty#36f8c871 id:long = Document; 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; help.support#17c6b5f6 phone_number:string user:User = help.Support;
@ -502,7 +508,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess
webPageEmpty#eb1477e8 id:long = WebPage; webPageEmpty#eb1477e8 id:long = WebPage;
webPagePending#c586da1c id:long date:int = WebPage; webPagePending#c586da1c id:long date:int = WebPage;
webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector<WebPageAttribute> = WebPage;
webPageNotModified#85849473 = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage;
authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
@ -523,11 +529,13 @@ chatInviteExported#fc2e05bc link:string = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet;
inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet;
@ -612,11 +620,6 @@ channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector
help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService; help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
messages.foundGifs#450a1c0a next_offset:int results:Vector<FoundGif> = messages.FoundGifs;
messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs;
messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs; messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
@ -645,7 +648,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off
exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink;
messageFwdHeader#ec338270 flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader; messageFwdHeader#353a686b flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader;
auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeSms#72a3158c = auth.CodeType;
auth.codeTypeCall#741cd3e3 = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType;
@ -686,8 +689,8 @@ contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers;
messages.featuredStickers#f89d88e5 hash:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers; messages.featuredStickers#b6abc341 hash:int count:int sets:Vector<StickerSetCovered> unread:Vector<long> = messages.FeaturedStickers;
messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers;
messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers; messages.recentStickers#22f3afb3 hash:int packs:Vector<StickerPack> stickers:Vector<Document> dates:Vector<int> = messages.RecentStickers;
@ -815,15 +818,16 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co
inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall;
phoneCallEmpty#5366c915 id:long = PhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall;
phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; phoneCallWaiting#1b8f4ad1 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallRequested#87eabb53 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; phoneCallAccepted#997c454a flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall; phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection;
phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector<string> = PhoneCallProtocol;
phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall; phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
@ -906,9 +910,6 @@ fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
inputClientProxy#75588b3f address:string port:int = InputClientProxy; inputClientProxy#75588b3f address:string port:int = InputClientProxy;
help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate; help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate; help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
@ -1009,7 +1010,7 @@ pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageLis
pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle; pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle;
page#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page; page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> views:flags.3?int = Page;
help.supportName#8c05f1c9 name:string = help.SupportName; help.supportName#8c05f1c9 name:string = help.SupportName;
@ -1018,11 +1019,11 @@ help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:strin
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> = Poll; poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector<PollAnswer> close_period:flags.4?int close_date:flags.5?int = Poll;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters;
pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> = PollResults; pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int recent_voters:flags.3?Vector<int> solution:flags.4?string solution_entities:flags.4?Vector<MessageEntity> = PollResults;
chatOnlines#f041e250 onlines:int = ChatOnlines; chatOnlines#f041e250 onlines:int = ChatOnlines;
@ -1116,11 +1117,44 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl;
payments.bankCardData#3e24e573 title:string open_urls:Vector<BankCardOpenUrl> = payments.BankCardData; payments.bankCardData#3e24e573 title:string open_urls:Vector<BankCardOpenUrl> = payments.BankCardData;
dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector<InputPeer> include_peers:Vector<InputPeer> exclude_peers:Vector<InputPeer> = DialogFilter;
dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested;
statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays;
statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev;
statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue;
statsGraphAsync#4a27eb2d token:string = StatsGraph;
statsGraphError#bedc9822 error:string = StatsGraph;
statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph;
messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters;
stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector<MessageInteractionCounters> = stats.BroadcastStats;
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#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize;
statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster;
statsGroupTopAdmin#6014f412 user_id:int deleted:int kicked:int banned:int = StatsGroupTopAdmin;
statsGroupTopInviter#31962a4c user_id:int invitations:int = StatsGroupTopInviter;
stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector<StatsGroupTopPoster> top_admins:Vector<StatsGroupTopAdmin> top_inviters:Vector<StatsGroupTopInviter> users:Vector<User> = stats.MegagroupStats;
globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings;
---functions--- ---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X; invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X; initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
@ -1210,6 +1244,8 @@ account.getThemes#285946f8 format:string hash:int = account.Themes;
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
account.getContentSettings#8b9b4dae = account.ContentSettings; account.getContentSettings#8b9b4dae = account.ContentSettings;
account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>; account.getMultiWallPapers#65ad71dc wallpapers:Vector<InputWallPaper> = Vector<WallPaper>;
account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings;
account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull; users.getFullUser#ca30a5b1 id:InputUser = UserFull;
@ -1285,7 +1321,6 @@ messages.migrateChat#15a3b8e3 chat_id:int = Updates;
messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector<long> = Bool;
messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document;
messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs;
messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
@ -1354,13 +1389,18 @@ messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector<int> = Updates;
messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector<int> = Updates;
messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList; messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList;
messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool; messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector<InputStickerSet> = Bool;
messages.getDialogFilters#f19ed96d = Vector<DialogFilter>;
messages.getSuggestedDialogFilters#a29cd42c = Vector<DialogFilterSuggested>;
messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool;
messages.updateDialogFiltersOrder#c563c1e4 order:Vector<int> = Bool;
messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers;
updates.getState#edd4882a = updates.State; updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo;
photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo; photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>; photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
@ -1382,7 +1422,6 @@ help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig; help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
help.getProxyData#3d7758e1 = help.ProxyData;
help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
@ -1392,6 +1431,9 @@ help.getPassportConfig#c661ad08 hash:int = help.PassportConfig;
help.getSupportName#d360e72c = help.SupportName; help.getSupportName#d360e72c = help.SupportName;
help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
help.getPromoData#c0977421 = help.PromoData;
help.hidePromoData#1e251c95 peer:InputPeer = Bool;
help.dismissSuggestion#77fa99f suggestion:string = Bool;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@ -1431,6 +1473,7 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
bots.setBotCommands#805d46f6 commands:Vector<BotCommand> = Bool;
payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm; payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt; payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
@ -1440,10 +1483,11 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
payments.getBankCardData#2e79d779 number:string = payments.BankCardData; payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector<InputStickerSetItem> = messages.StickerSet; stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet;
phone.getCallConfig#55451fa9 = DataJSON; phone.getCallConfig#55451fa9 = DataJSON;
phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall;
@ -1453,6 +1497,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.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.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; 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.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
@ -1463,4 +1508,8 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua
folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates; folders.editPeerFolders#6847d0ab folder_peers:Vector<InputFolderPeer> = Updates;
folders.deleteFolder#1c295881 folder_id:int = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates;
// LAYER 110 stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats;
stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph;
stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats;
// LAYER 117

View File

@ -15,9 +15,13 @@ 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_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_KEY_UNREGISTERED,401,The key is not registered in the system
AUTH_RESTART,500,Restart the authorization process 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" 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 BOTS_TOO_MUCH,400,There are too many bots in this chat/channel
BOT_CHANNELS_NA,400,Bots can't edit admin privileges 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_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_INLINE_DISABLED,400,This bot can't be used in inline mode
BOT_INVALID,400,This is not a valid bot BOT_INVALID,400,This is not a valid bot
@ -25,8 +29,11 @@ BOT_METHOD_INVALID,400,The API access for bot users is restricted. The method yo
BOT_MISSING,400,This method can only be run by 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_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_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_FORBIDDEN,403,The request cannot be used in broadcast channels
BROADCAST_ID_INVALID,400,The channel is invalid BROADCAST_ID_INVALID,400,The channel is invalid
BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public BROADCAST_PUBLIC_VOTERS_FORBIDDEN,400,You cannot broadcast polls where the voters are public
BROADCAST_REQUIRED,400,The request can only be used with a broadcast channel
BUTTON_DATA_INVALID,400,The provided button data is invalid BUTTON_DATA_INVALID,400,The provided button data is invalid
BUTTON_TYPE_INVALID,400,The type of one of the buttons you provided is invalid BUTTON_TYPE_INVALID,400,The type of one of the buttons you provided is invalid
BUTTON_URL_INVALID,400,Button URL invalid BUTTON_URL_INVALID,400,Button URL invalid
@ -67,7 +74,9 @@ 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_LAYER_INVALID,400,The very first request must always be InvokeWithLayerRequest
CONNECTION_NOT_INITED,400,Connection not initialized CONNECTION_NOT_INITED,400,Connection not initialized
CONNECTION_SYSTEM_EMPTY,400,Connection system empty 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_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 DATA_INVALID,400,Encrypted data invalid
DATA_JSON_INVALID,400,The provided JSON data is invalid DATA_JSON_INVALID,400,The provided JSON data is invalid
DATE_EMPTY,400,Date empty DATE_EMPTY,400,Date empty
@ -77,6 +86,7 @@ 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_X,400,"Email unconfirmed, the length of the code must be {code_length}"
EMOTICON_EMPTY,400,The emoticon field cannot be empty EMOTICON_EMPTY,400,The emoticon field cannot be empty
EMOTICON_INVALID,400,The specified emoticon cannot be used or was not a emoticon
ENCRYPTED_MESSAGE_INVALID,400,Encrypted message invalid ENCRYPTED_MESSAGE_INVALID,400,Encrypted message invalid
ENCRYPTION_ALREADY_ACCEPTED,400,Secret chat already accepted ENCRYPTION_ALREADY_ACCEPTED,400,Secret chat already accepted
ENCRYPTION_ALREADY_DECLINED,400,The secret chat was already declined ENCRYPTION_ALREADY_DECLINED,400,The secret chat was already declined
@ -100,6 +110,8 @@ 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_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 FILEREF_UPGRADE_NEEDED,406,The file reference needs to be refreshed before being used again
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_X,420,A wait of {seconds} seconds is required in the test servers
@ -107,6 +119,7 @@ FLOOD_WAIT_X,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
FRESH_CHANGE_PHONE_FORBIDDEN,406,Recently logged-in users cannot use this request
FRESH_RESET_AUTHORISATION_FORBIDDEN,406,The current session is too new and cannot be used to reset other authorisations yet FRESH_RESET_AUTHORISATION_FORBIDDEN,406,The current session is too new and cannot be used to reset other authorisations yet
GAME_BOT_INVALID,400,You cannot send that game with the current bot GAME_BOT_INVALID,400,You cannot send that game with the current bot
GIF_ID_INVALID,400,The provided GIF ID is invalid GIF_ID_INVALID,400,The provided GIF ID is invalid
@ -114,6 +127,7 @@ GROUPED_MEDIA_INVALID,400,Invalid grouped media
HASH_INVALID,400,The provided hash is invalid HASH_INVALID,400,The provided hash is invalid
HISTORY_GET_FAILED,500,Fetching of history failed HISTORY_GET_FAILED,500,Fetching of history failed
IMAGE_PROCESS_FAILED,400,Failure while processing image IMAGE_PROCESS_FAILED,400,Failure while processing image
INLINE_BOT_REQUIRED,403,The action must be performed through an inline bot callback
INLINE_RESULT_EXPIRED,400,The inline query expired INLINE_RESULT_EXPIRED,400,The inline query expired
INPUT_CONSTRUCTOR_INVALID,400,The provided constructor is invalid INPUT_CONSTRUCTOR_INVALID,400,The provided constructor is invalid
INPUT_FETCH_ERROR,,An error occurred while deserializing TL parameters INPUT_FETCH_ERROR,,An error occurred while deserializing TL parameters
@ -142,6 +156,7 @@ MEDIA_NEW_INVALID,400,The new media to edit the message with is invalid (such as
MEDIA_PREV_INVALID,400,The old media cannot be edited with anything else (such as stickers or voice notes) MEDIA_PREV_INVALID,400,The old media cannot be edited with anything else (such as stickers or voice notes)
MEGAGROUP_ID_INVALID,400,The group is invalid MEGAGROUP_ID_INVALID,400,The group is invalid
MEGAGROUP_PREHISTORY_HIDDEN,400,You can't set this discussion group because it's history is hidden MEGAGROUP_PREHISTORY_HIDDEN,400,You can't set this discussion group because it's history is hidden
MEGAGROUP_REQUIRED,400,The request can only be used with a megagroup channel
MEMBER_NO_LOCATION,500,An internal failure occurred while fetching user info (couldn't find location) MEMBER_NO_LOCATION,500,An internal failure occurred while fetching user info (couldn't find location)
MEMBER_OCCUPY_PRIMARY_LOC_FAILED,500,Occupation of primary member location failed MEMBER_OCCUPY_PRIMARY_LOC_FAILED,500,Occupation of primary member location failed
MESSAGE_AUTHOR_REQUIRED,403,Message author required MESSAGE_AUTHOR_REQUIRED,403,Message author required
@ -153,6 +168,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_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_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 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 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_ID_INVALID,400,The message ID used in the peer was invalid
MSG_WAIT_FAILED,400,A waiting call returned an error MSG_WAIT_FAILED,400,A waiting call returned an error
@ -173,7 +189,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 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_EMPTY,400,The provided password is empty
PASSWORD_HASH_INVALID,400,The password (and thus its hash value) you entered is invalid 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_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 PAYMENT_PROVIDER_INVALID,400,The payment provider was not recognised or its token was invalid
PEER_FLOOD,,Too many requests PEER_FLOOD,,Too many requests
PEER_ID_INVALID,400,An invalid Peer was used. Make sure to pass the right peer type PEER_ID_INVALID,400,An invalid Peer was used. Make sure to pass the right peer type
@ -208,6 +226,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
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
PTS_CHANGE_EMPTY,500,No PTS change PTS_CHANGE_EMPTY,500,No PTS change
QUERY_ID_EMPTY,400,The query ID is empty QUERY_ID_EMPTY,400,The query ID is empty
QUERY_ID_INVALID,400,The query ID is invalid QUERY_ID_INVALID,400,The query ID is invalid
@ -234,6 +253,7 @@ RPC_MCGET_FAIL,,"Telegram is having internal issues, please try again later."
RSA_DECRYPT_FAILED,400,Internal RSA decryption failed RSA_DECRYPT_FAILED,400,Internal RSA decryption failed
SCHEDULE_BOT_NOT_ALLOWED,400,Bots are not allowed to schedule messages SCHEDULE_BOT_NOT_ALLOWED,400,Bots are not allowed to schedule messages
SCHEDULE_DATE_TOO_LATE,400,The date you tried to schedule is too far in the future (last known limit of 1 year and a few hours) SCHEDULE_DATE_TOO_LATE,400,The date you tried to schedule is too far in the future (last known limit of 1 year and a few hours)
SCHEDULE_STATUS_PRIVATE,400,You cannot schedule a message until the person comes online if their privacy does not show this information
SCHEDULE_TOO_MUCH,400,You cannot schedule more messages in this chat (last known limit of 100 per chat) SCHEDULE_TOO_MUCH,400,You cannot schedule more messages in this chat (last known limit of 100 per chat)
SEARCH_QUERY_EMPTY,400,The search query is empty SEARCH_QUERY_EMPTY,400,The search query is empty
SECONDS_INVALID,400,"Slow mode only supports certain values (e.g. 0, 10s, 30s, 1m, 5m, 15m and 1h)" SECONDS_INVALID,400,"Slow mode only supports certain values (e.g. 0, 10s, 30s, 1m, 5m, 15m and 1h)"
@ -242,11 +262,13 @@ SEND_MESSAGE_TYPE_INVALID,400,The message type is invalid
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
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
SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat SLOWMODE_WAIT_X,420,A wait of {seconds} seconds is required before sending another message in this chat
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}
STICKERSET_INVALID,400,The provided sticker set is invalid STICKERSET_INVALID,400,The provided sticker set is invalid
STICKERS_EMPTY,400,No sticker provided STICKERS_EMPTY,400,No sticker provided
STICKER_EMOJI_INVALID,400,Sticker emoji invalid STICKER_EMOJI_INVALID,400,Sticker emoji invalid
@ -298,9 +320,11 @@ USER_NOT_PARTICIPANT,400,The target user is not a member of the specified megagr
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
USER_RESTRICTED,403,"You're spamreported, you can't create channels or chats." USER_RESTRICTED,403,"You're spamreported, you can't create channels or chats."
VIDEO_CONTENT_TYPE_INVALID,400,The video content type is not supported with the given parameters (i.e. supports_streaming) VIDEO_CONTENT_TYPE_INVALID,400,The video content type is not supported with the given parameters (i.e. supports_streaming)
VIDEO_FILE_INVALID,400,The given video cannot be used
WALLPAPER_FILE_INVALID,400,The given file cannot be used as a wallpaper WALLPAPER_FILE_INVALID,400,The given file cannot be used as a wallpaper
WALLPAPER_INVALID,400,The input wallpaper was not valid WALLPAPER_INVALID,400,The input wallpaper was not valid
WC_CONVERT_URL_INVALID,400,WC convert URL invalid WC_CONVERT_URL_INVALID,400,WC convert URL invalid
WEBDOCUMENT_URL_INVALID,400,The given URL cannot be used
WEBPAGE_CURL_FAILED,400,Failure while fetching the webpage with cURL WEBPAGE_CURL_FAILED,400,Failure while fetching the webpage with cURL
WEBPAGE_MEDIA_EMPTY,400,Webpage media empty WEBPAGE_MEDIA_EMPTY,400,Webpage media empty
WORKER_BUSY_TOO_LONG_RETRY,500,Telegram workers are too busy to respond immediately WORKER_BUSY_TOO_LONG_RETRY,500,Telegram workers are too busy to respond immediately

Can't render this file because it has a wrong number of fields in line 103.

View File

@ -5,24 +5,31 @@ account.changePhone,user,PHONE_NUMBER_INVALID
account.checkUsername,user,USERNAME_INVALID account.checkUsername,user,USERNAME_INVALID
account.confirmPasswordEmail,user, account.confirmPasswordEmail,user,
account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY account.confirmPhone,user,CODE_HASH_INVALID PHONE_CODE_EMPTY
account.createTheme,user,
account.deleteSecureValue,user, account.deleteSecureValue,user,
account.finishTakeoutSession,user, account.finishTakeoutSession,user,
account.getAccountTTL,user, account.getAccountTTL,user,
account.getAllSecureValues,user, account.getAllSecureValues,user,
account.getAuthorizationForm,user, account.getAuthorizationForm,user,
account.getAuthorizations,user, account.getAuthorizations,user,
account.getAutoDownloadSettings,user,
account.getContactSignUpNotification,user, account.getContactSignUpNotification,user,
account.getContentSettings,user,
account.getMultiWallPapers,user,
account.getNotifyExceptions,user, account.getNotifyExceptions,user,
account.getNotifySettings,user,PEER_ID_INVALID account.getNotifySettings,user,PEER_ID_INVALID
account.getPassword,user, account.getPassword,user,
account.getPasswordSettings,user,PASSWORD_HASH_INVALID account.getPasswordSettings,user,PASSWORD_HASH_INVALID
account.getPrivacy,user,PRIVACY_KEY_INVALID account.getPrivacy,user,PRIVACY_KEY_INVALID
account.getSecureValue,user, account.getSecureValue,user,
account.getTheme,user,
account.getThemes,user,
account.getTmpPassword,user,PASSWORD_HASH_INVALID TMP_PASSWORD_DISABLED account.getTmpPassword,user,PASSWORD_HASH_INVALID TMP_PASSWORD_DISABLED
account.getWallPaper,user,WALLPAPER_INVALID account.getWallPaper,user,WALLPAPER_INVALID
account.getWallPapers,user, account.getWallPapers,user,
account.getWebAuthorizations,user, account.getWebAuthorizations,user,
account.initTakeoutSession,user, account.initTakeoutSession,user,
account.installTheme,user,
account.installWallPaper,user,WALLPAPER_INVALID account.installWallPaper,user,WALLPAPER_INVALID
account.registerDevice,user,TOKEN_INVALID account.registerDevice,user,TOKEN_INVALID
account.reportPeer,user,PEER_ID_INVALID account.reportPeer,user,PEER_ID_INVALID
@ -32,32 +39,40 @@ account.resetNotifySettings,user,
account.resetWallPapers,user, account.resetWallPapers,user,
account.resetWebAuthorization,user, account.resetWebAuthorization,user,
account.resetWebAuthorizations,user, account.resetWebAuthorizations,user,
account.saveAutoDownloadSettings,user,
account.saveSecureValue,user,PASSWORD_REQUIRED account.saveSecureValue,user,PASSWORD_REQUIRED
account.saveTheme,user,
account.saveWallPaper,user,WALLPAPER_INVALID account.saveWallPaper,user,WALLPAPER_INVALID
account.sendChangePhoneCode,user,PHONE_NUMBER_INVALID account.sendChangePhoneCode,user,FRESH_CHANGE_PHONE_FORBIDDEN PHONE_NUMBER_INVALID
account.sendConfirmPhoneCode,user,HASH_INVALID account.sendConfirmPhoneCode,user,HASH_INVALID
account.sendVerifyEmailCode,user,EMAIL_INVALID account.sendVerifyEmailCode,user,EMAIL_INVALID
account.sendVerifyPhoneCode,user, account.sendVerifyPhoneCode,user,
account.setAccountTTL,user,TTL_DAYS_INVALID account.setAccountTTL,user,TTL_DAYS_INVALID
account.setContactSignUpNotification,user, account.setContactSignUpNotification,user,
account.setPrivacy,user,PRIVACY_KEY_INVALID account.setContentSettings,user,
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_X 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,
account.updateUsername,user,USERNAME_INVALID USERNAME_NOT_MODIFIED USERNAME_OCCUPIED account.updateUsername,user,USERNAME_INVALID USERNAME_NOT_MODIFIED USERNAME_OCCUPIED
account.uploadTheme,user,
account.uploadWallPaper,user,WALLPAPER_FILE_INVALID account.uploadWallPaper,user,WALLPAPER_FILE_INVALID
account.verifyEmail,user,EMAIL_INVALID account.verifyEmail,user,EMAIL_INVALID
account.verifyPhone,user, account.verifyPhone,user,
auth.acceptLoginToken,user,
auth.bindTempAuthKey,both,ENCRYPTED_MESSAGE_INVALID INPUT_REQUEST_TOO_LONG TEMP_AUTH_KEY_EMPTY Timeout auth.bindTempAuthKey,both,ENCRYPTED_MESSAGE_INVALID INPUT_REQUEST_TOO_LONG TEMP_AUTH_KEY_EMPTY Timeout
auth.cancelCode,user,PHONE_NUMBER_INVALID auth.cancelCode,user,PHONE_NUMBER_INVALID
auth.checkPassword,user,PASSWORD_HASH_INVALID auth.checkPassword,user,PASSWORD_HASH_INVALID
auth.dropTempAuthKeys,both, auth.dropTempAuthKeys,both,
auth.exportAuthorization,both,DC_ID_INVALID auth.exportAuthorization,both,DC_ID_INVALID
auth.exportLoginToken,user,
auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID auth.importAuthorization,both,AUTH_BYTES_INVALID USER_ID_INVALID
auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID auth.importBotAuthorization,both,ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID
auth.importLoginToken,user,AUTH_TOKEN_ALREADY_ACCEPTED AUTH_TOKEN_EXPIRED AUTH_TOKEN_INVALID
auth.logOut,both, auth.logOut,both,
auth.recoverPassword,user,CODE_EMPTY auth.recoverPassword,user,CODE_EMPTY
auth.requestPasswordRecovery,user,PASSWORD_EMPTY auth.requestPasswordRecovery,user,PASSWORD_EMPTY
@ -68,6 +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 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.answerWebhookJSONQuery,bot,QUERY_ID_INVALID USER_BOT_INVALID
bots.sendCustomRequest,bot,USER_BOT_INVALID bots.sendCustomRequest,bot,USER_BOT_INVALID
bots.setBotCommands,bot,BOT_COMMAND_DESCRIPTION_INVALID
channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID channels.checkUsername,user,CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID
channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED channels.createChannel,user,CHAT_TITLE_EMPTY USER_RESTRICTED
channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE channels.deleteChannel,user,CHANNEL_INVALID CHANNEL_PRIVATE
@ -76,6 +92,8 @@ 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
channels.editLocation,user,
channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID channels.editPhoto,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID
channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED channels.editTitle,both,CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
channels.exportMessageLink,user,CHANNEL_INVALID channels.exportMessageLink,user,CHANNEL_INVALID
@ -83,6 +101,8 @@ channels.getAdminLog,user,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED
channels.getAdminedPublicChannels,user, channels.getAdminedPublicChannels,user,
channels.getChannels,both,CHANNEL_INVALID CHANNEL_PRIVATE NEED_CHAT_INVALID channels.getChannels,both,CHANNEL_INVALID CHANNEL_PRIVATE NEED_CHAT_INVALID
channels.getFullChannel,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA Timeout channels.getFullChannel,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA Timeout
channels.getGroupsForDiscussion,user,
channels.getInactiveChannels,user,
channels.getLeftChannels,user, channels.getLeftChannels,user,
channels.getMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_IDS_EMPTY channels.getMessages,both,CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_IDS_EMPTY
channels.getParticipant,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ID_INVALID USER_NOT_PARTICIPANT channels.getParticipant,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ID_INVALID USER_NOT_PARTICIPANT
@ -99,13 +119,15 @@ channels.togglePreHistoryHidden,user,CHAT_LINK_EXISTS
channels.toggleSignatures,user,CHANNEL_INVALID channels.toggleSignatures,user,CHANNEL_INVALID
channels.toggleSlowMode,user,SECONDS_INVALID channels.toggleSlowMode,user,SECONDS_INVALID
channels.updateUsername,user,CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED channels.updateUsername,user,CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED
contacts.acceptContact,user,
contacts.addContact,user,CONTACT_NAME_EMPTY
contacts.block,user,CONTACT_ID_INVALID contacts.block,user,CONTACT_ID_INVALID
contacts.deleteByPhones,user, contacts.deleteByPhones,user,
contacts.deleteContact,user,CONTACT_ID_INVALID
contacts.deleteContacts,user,NEED_MEMBER_INVALID Timeout contacts.deleteContacts,user,NEED_MEMBER_INVALID Timeout
contacts.getBlocked,user, contacts.getBlocked,user,
contacts.getContactIDs,user, contacts.getContactIDs,user,
contacts.getContacts,user, contacts.getContacts,user,
contacts.getLocated,user,
contacts.getSaved,user,TAKEOUT_REQUIRED contacts.getSaved,user,TAKEOUT_REQUIRED
contacts.getStatuses,user, contacts.getStatuses,user,
contacts.getTopPeers,user,TYPES_EMPTY contacts.getTopPeers,user,TYPES_EMPTY
@ -116,9 +138,9 @@ contacts.resolveUsername,both,AUTH_KEY_PERM_EMPTY SESSION_PASSWORD_NEEDED USERNA
contacts.search,user,QUERY_TOO_SHORT SEARCH_QUERY_EMPTY Timeout contacts.search,user,QUERY_TOO_SHORT SEARCH_QUERY_EMPTY Timeout
contacts.toggleTopPeers,user, contacts.toggleTopPeers,user,
contacts.unblock,user,CONTACT_ID_INVALID contacts.unblock,user,CONTACT_ID_INVALID
contest.saveDeveloperInfo,both,
folders.deleteFolder,user,FOLDER_ID_EMPTY folders.deleteFolder,user,FOLDER_ID_EMPTY
folders.editPeerFolders,user,FOLDER_ID_INVALID folders.editPeerFolders,user,FOLDER_ID_INVALID
getFutureSalts,both,
help.acceptTermsOfService,user, help.acceptTermsOfService,user,
help.editUserInfo,user,USER_INVALID help.editUserInfo,user,USER_INVALID
help.getAppChangelog,user, help.getAppChangelog,user,
@ -151,6 +173,7 @@ langpack.getLanguage,user,
langpack.getLanguages,user,LANG_PACK_INVALID langpack.getLanguages,user,LANG_PACK_INVALID
langpack.getStrings,user,LANG_PACK_INVALID langpack.getStrings,user,LANG_PACK_INVALID
messages.acceptEncryption,user,CHAT_ID_INVALID ENCRYPTION_ALREADY_ACCEPTED ENCRYPTION_ALREADY_DECLINED ENCRYPTION_OCCUPY_FAILED messages.acceptEncryption,user,CHAT_ID_INVALID ENCRYPTION_ALREADY_ACCEPTED ENCRYPTION_ALREADY_DECLINED ENCRYPTION_OCCUPY_FAILED
messages.acceptUrlAuth,user,
messages.addChatUser,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID USERS_TOO_MUCH USER_ALREADY_PARTICIPANT USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED messages.addChatUser,user,CHAT_ADMIN_REQUIRED CHAT_ID_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID USERS_TOO_MUCH USER_ALREADY_PARTICIPANT USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
messages.checkChatInvite,user,INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID messages.checkChatInvite,user,INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID
messages.clearAllDrafts,user, messages.clearAllDrafts,user,
@ -159,6 +182,7 @@ messages.createChat,user,USERS_TOO_FEW USER_RESTRICTED
messages.deleteChatUser,both,CHAT_ID_INVALID PEER_ID_INVALID USER_NOT_PARTICIPANT messages.deleteChatUser,both,CHAT_ID_INVALID PEER_ID_INVALID USER_NOT_PARTICIPANT
messages.deleteHistory,user,PEER_ID_INVALID messages.deleteHistory,user,PEER_ID_INVALID
messages.deleteMessages,both,MESSAGE_DELETE_FORBIDDEN messages.deleteMessages,both,MESSAGE_DELETE_FORBIDDEN
messages.deleteScheduledMessages,user,
messages.discardEncryption,user,CHAT_ID_EMPTY ENCRYPTION_ALREADY_DECLINED ENCRYPTION_ID_INVALID messages.discardEncryption,user,CHAT_ID_EMPTY ENCRYPTION_ALREADY_DECLINED ENCRYPTION_ID_INVALID
messages.editChatAbout,both, messages.editChatAbout,both,
messages.editChatAdmin,user,CHAT_ID_INVALID messages.editChatAdmin,user,CHAT_ID_INVALID
@ -166,8 +190,8 @@ messages.editChatDefaultBannedRights,both,BANNED_RIGHTS_INVALID
messages.editChatPhoto,both,CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETCH_FAIL PEER_ID_INVALID PHOTO_EXT_INVALID messages.editChatPhoto,both,CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETCH_FAIL PEER_ID_INVALID PHOTO_EXT_INVALID
messages.editChatTitle,both,CHAT_ID_INVALID NEED_CHAT_INVALID messages.editChatTitle,both,CHAT_ID_INVALID NEED_CHAT_INVALID
messages.editInlineBotMessage,both,MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED messages.editInlineBotMessage,both,MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED
messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID messages.editMessage,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INLINE_BOT_REQUIRED INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID
messages.exportChatInvite,user,CHAT_ID_INVALID messages.exportChatInvite,both,CHAT_ID_INVALID
messages.faveSticker,user,STICKER_ID_INVALID messages.faveSticker,user,STICKER_ID_INVALID
messages.forwardMessages,both,BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER messages.forwardMessages,both,BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
messages.getAllChats,user, messages.getAllChats,user,
@ -175,13 +199,18 @@ messages.getAllDrafts,user,
messages.getAllStickers,user, messages.getAllStickers,user,
messages.getArchivedStickers,user, messages.getArchivedStickers,user,
messages.getAttachedStickers,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.getChats,both,CHAT_ID_INVALID PEER_ID_INVALID
messages.getCommonChats,user,USER_ID_INVALID messages.getCommonChats,user,USER_ID_INVALID
messages.getDhConfig,user,RANDOM_LENGTH_INVALID messages.getDhConfig,user,RANDOM_LENGTH_INVALID
messages.getDialogFilters,user,
messages.getDialogUnreadMarks,user, messages.getDialogUnreadMarks,user,
messages.getDialogs,user,INPUT_CONSTRUCTOR_INVALID OFFSET_PEER_ID_INVALID SESSION_PASSWORD_NEEDED Timeout messages.getDialogs,user,INPUT_CONSTRUCTOR_INVALID OFFSET_PEER_ID_INVALID SESSION_PASSWORD_NEEDED Timeout
messages.getDocumentByHash,both,SHA256_HASH_INVALID messages.getDocumentByHash,both,SHA256_HASH_INVALID
messages.getEmojiKeywords,user,
messages.getEmojiKeywordsDifference,user,
messages.getEmojiKeywordsLanguages,user,
messages.getEmojiURL,user,
messages.getFavedStickers,user, messages.getFavedStickers,user,
messages.getFeaturedStickers,user, messages.getFeaturedStickers,user,
messages.getFullChat,both,CHAT_ID_INVALID PEER_ID_INVALID messages.getFullChat,both,CHAT_ID_INVALID PEER_ID_INVALID
@ -198,17 +227,22 @@ messages.getPeerDialogs,user,CHANNEL_PRIVATE PEER_ID_INVALID
messages.getPeerSettings,user,CHANNEL_INVALID PEER_ID_INVALID messages.getPeerSettings,user,CHANNEL_INVALID PEER_ID_INVALID
messages.getPinnedDialogs,user, messages.getPinnedDialogs,user,
messages.getPollResults,user, messages.getPollResults,user,
messages.getPollVotes,user,BROADCAST_FORBIDDEN
messages.getRecentLocations,user, messages.getRecentLocations,user,
messages.getRecentStickers,user, messages.getRecentStickers,user,
messages.getSavedGifs,user, messages.getSavedGifs,user,
messages.getScheduledHistory,user,
messages.getScheduledMessages,user,
messages.getSearchCounters,user,
messages.getSplitRanges,user, messages.getSplitRanges,user,
messages.getStatsURL,user, messages.getStatsURL,user,
messages.getStickerSet,both,STICKERSET_INVALID messages.getStickerSet,both,STICKERSET_INVALID
messages.getStickers,user,EMOTICON_EMPTY messages.getStickers,user,EMOTICON_EMPTY
messages.getSuggestedDialogFilters,user,
messages.getUnreadMentions,user,PEER_ID_INVALID messages.getUnreadMentions,user,PEER_ID_INVALID
messages.getWebPage,user,WC_CONVERT_URL_INVALID messages.getWebPage,user,WC_CONVERT_URL_INVALID
messages.getWebPagePreview,user, messages.getWebPagePreview,user,
messages.hideReportSpam,user,PEER_ID_INVALID 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.installStickerSet,user,STICKERSET_INVALID messages.installStickerSet,user,STICKERSET_INVALID
messages.markDialogUnread,user, messages.markDialogUnread,user,
@ -226,37 +260,42 @@ messages.report,user,
messages.reportEncryptedSpam,user,CHAT_ID_INVALID messages.reportEncryptedSpam,user,CHAT_ID_INVALID
messages.reportSpam,user,PEER_ID_INVALID messages.reportSpam,user,PEER_ID_INVALID
messages.requestEncryption,user,DH_G_A_INVALID USER_ID_INVALID messages.requestEncryption,user,DH_G_A_INVALID USER_ID_INVALID
messages.requestUrlAuth,user,
messages.saveDraft,user,PEER_ID_INVALID messages.saveDraft,user,PEER_ID_INVALID
messages.saveGif,user,GIF_ID_INVALID messages.saveGif,user,GIF_ID_INVALID
messages.saveRecentSticker,user,STICKER_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.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.searchGlobal,user,SEARCH_QUERY_EMPTY
messages.searchStickerSets,user, messages.searchStickerSets,user,
messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED messages.sendEncrypted,user,CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED
messages.sendEncryptedFile,user,MSG_WAIT_FAILED messages.sendEncryptedFile,user,MSG_WAIT_FAILED
messages.sendEncryptedService,user,DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED 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.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 EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID 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_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER 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.sendMultiMedia,both,SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH
messages.sendReaction,User,REACTION_INVALID messages.sendScheduledMessages,user,
messages.sendVote,user,MESSAGE_POLL_CLOSED OPTION_INVALID messages.sendVote,user,MESSAGE_POLL_CLOSED OPTION_INVALID
messages.setBotCallbackAnswer,both,QUERY_ID_INVALID URL_INVALID messages.setBotCallbackAnswer,both,QUERY_ID_INVALID URL_INVALID
messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY messages.setBotPrecheckoutResults,both,ERROR_TEXT_EMPTY
messages.setBotShippingResults,both,QUERY_ID_INVALID messages.setBotShippingResults,both,QUERY_ID_INVALID
messages.setEncryptedTyping,user,CHAT_ID_INVALID messages.setEncryptedTyping,user,CHAT_ID_INVALID
messages.setGameScore,bot,PEER_ID_INVALID USER_BOT_REQUIRED messages.setGameScore,bot,PEER_ID_INVALID USER_BOT_REQUIRED
messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID messages.setInlineBotResults,bot,ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID WEBDOCUMENT_URL_INVALID
messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED messages.setInlineGameScore,bot,MESSAGE_ID_INVALID USER_BOT_REQUIRED
messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT messages.setTyping,both,CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT
messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID messages.startBot,user,BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID
messages.toggleDialogPin,user,PEER_ID_INVALID messages.toggleDialogPin,user,PEER_ID_INVALID
messages.toggleStickerSets,user,
messages.uninstallStickerSet,user,STICKERSET_INVALID messages.uninstallStickerSet,user,STICKERSET_INVALID
messages.updateDialogFilter,user,
messages.updateDialogFiltersOrder,user,
messages.updatePinnedMessage,both, messages.updatePinnedMessage,both,
messages.uploadEncryptedFile,user, messages.uploadEncryptedFile,user,
messages.uploadMedia,both,BOT_MISSING MEDIA_INVALID PEER_ID_INVALID messages.uploadMedia,both,BOT_MISSING MEDIA_INVALID PEER_ID_INVALID
payments.clearSavedInfo,user, payments.clearSavedInfo,user,
payments.getBankCardData,user,
payments.getPaymentForm,user,MESSAGE_ID_INVALID payments.getPaymentForm,user,MESSAGE_ID_INVALID
payments.getPaymentReceipt,user,MESSAGE_ID_INVALID payments.getPaymentReceipt,user,MESSAGE_ID_INVALID
payments.getSavedInfo,user, payments.getSavedInfo,user,
@ -273,17 +312,21 @@ phone.setCallRating,user,CALL_PEER_INVALID
photos.deletePhotos,user, photos.deletePhotos,user,
photos.getUserPhotos,both,MAX_ID_INVALID USER_ID_INVALID photos.getUserPhotos,both,MAX_ID_INVALID USER_ID_INVALID
photos.updateProfilePhoto,user, photos.updateProfilePhoto,user,
photos.uploadProfilePhoto,user,FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID photos.uploadProfilePhoto,user,FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID VIDEO_FILE_INVALID
ping,both, ping,both,
reqDHParams,both, reqDHParams,both,
reqPq,both, reqPq,both,
reqPqMulti,both, reqPqMulti,both,
rpcDropAnswer,both, rpcDropAnswer,both,
setClientDHParams,both, setClientDHParams,both,
stats.getBroadcastStats,user,BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED STATS_MIGRATE_X
stats.getMegagroupStats,user,CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_X
stats.loadAsyncGraph,user,
stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID stickers.addStickerToSet,bot,BOT_MISSING STICKERSET_INVALID
stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID stickers.changeStickerPosition,bot,BOT_MISSING STICKER_INVALID
stickers.createStickerSet,bot,BOT_MISSING PACK_SHORT_NAME_INVALID PACK_SHORT_NAME_OCCUPIED PEER_ID_INVALID SHORTNAME_OCCUPY_FAILED STICKERS_EMPTY STICKER_EMOJI_INVALID STICKER_FILE_INVALID STICKER_PNG_DIMENSIONS STICKER_PNG_NOPNG USER_ID_INVALID stickers.createStickerSet,bot,BOT_MISSING PACK_SHORT_NAME_INVALID PACK_SHORT_NAME_OCCUPIED PEER_ID_INVALID SHORTNAME_OCCUPY_FAILED STICKERS_EMPTY STICKER_EMOJI_INVALID STICKER_FILE_INVALID STICKER_PNG_DIMENSIONS STICKER_PNG_NOPNG USER_ID_INVALID
stickers.removeStickerFromSet,bot,BOT_MISSING STICKER_INVALID stickers.removeStickerFromSet,bot,BOT_MISSING STICKER_INVALID
stickers.setStickerSetThumb,bot,
updates.getChannelDifference,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA HISTORY_GET_FAILED PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID PERSISTENT_TIMESTAMP_OUTDATED RANGES_INVALID Timeout updates.getChannelDifference,both,CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA HISTORY_GET_FAILED PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID PERSISTENT_TIMESTAMP_OUTDATED RANGES_INVALID Timeout
updates.getDifference,both,AUTH_KEY_PERM_EMPTY CDN_METHOD_INVALID DATE_EMPTY NEED_MEMBER_INVALID PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID SESSION_PASSWORD_NEEDED STORE_INVALID_SCALAR_TYPE Timeout updates.getDifference,both,AUTH_KEY_PERM_EMPTY CDN_METHOD_INVALID DATE_EMPTY NEED_MEMBER_INVALID PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID SESSION_PASSWORD_NEEDED STORE_INVALID_SCALAR_TYPE Timeout
updates.getState,both,AUTH_KEY_DUPLICATED MSGID_DECREASE_RETRY SESSION_PASSWORD_NEEDED Timeout updates.getState,both,AUTH_KEY_DUPLICATED MSGID_DECREASE_RETRY SESSION_PASSWORD_NEEDED Timeout

1 method usability errors
5 account.checkUsername user USERNAME_INVALID
6 account.confirmPasswordEmail user
7 account.confirmPhone user CODE_HASH_INVALID PHONE_CODE_EMPTY
8 account.createTheme user
9 account.deleteSecureValue user
10 account.finishTakeoutSession user
11 account.getAccountTTL user
12 account.getAllSecureValues user
13 account.getAuthorizationForm user
14 account.getAuthorizations user
15 account.getAutoDownloadSettings user
16 account.getContactSignUpNotification user
17 account.getContentSettings user
18 account.getMultiWallPapers user
19 account.getNotifyExceptions user
20 account.getNotifySettings user PEER_ID_INVALID
21 account.getPassword user
22 account.getPasswordSettings user PASSWORD_HASH_INVALID
23 account.getPrivacy user PRIVACY_KEY_INVALID
24 account.getSecureValue user
25 account.getTheme user
26 account.getThemes user
27 account.getTmpPassword user PASSWORD_HASH_INVALID TMP_PASSWORD_DISABLED
28 account.getWallPaper user WALLPAPER_INVALID
29 account.getWallPapers user
30 account.getWebAuthorizations user
31 account.initTakeoutSession user
32 account.installTheme user
33 account.installWallPaper user WALLPAPER_INVALID
34 account.registerDevice user TOKEN_INVALID
35 account.reportPeer user PEER_ID_INVALID
39 account.resetWallPapers user
40 account.resetWebAuthorization user
41 account.resetWebAuthorizations user
42 account.saveAutoDownloadSettings user
43 account.saveSecureValue user PASSWORD_REQUIRED
44 account.saveTheme user
45 account.saveWallPaper user WALLPAPER_INVALID
46 account.sendChangePhoneCode user PHONE_NUMBER_INVALID FRESH_CHANGE_PHONE_FORBIDDEN PHONE_NUMBER_INVALID
47 account.sendConfirmPhoneCode user HASH_INVALID
48 account.sendVerifyEmailCode user EMAIL_INVALID
49 account.sendVerifyPhoneCode user
50 account.setAccountTTL user TTL_DAYS_INVALID
51 account.setContactSignUpNotification user
52 account.setPrivacy account.setContentSettings user PRIVACY_KEY_INVALID
53 account.setPrivacy user PRIVACY_KEY_INVALID PRIVACY_TOO_LONG
54 account.unregisterDevice user TOKEN_INVALID
55 account.updateDeviceLocked user
56 account.updateNotifySettings user PEER_ID_INVALID
57 account.updatePasswordSettings user EMAIL_UNCONFIRMED_X NEW_SALT_INVALID NEW_SETTINGS_INVALID PASSWORD_HASH_INVALID
58 account.updateProfile user ABOUT_TOO_LONG FIRSTNAME_INVALID
59 account.updateStatus user SESSION_PASSWORD_NEEDED
60 account.updateTheme user
61 account.updateUsername user USERNAME_INVALID USERNAME_NOT_MODIFIED USERNAME_OCCUPIED
62 account.uploadTheme user
63 account.uploadWallPaper user WALLPAPER_FILE_INVALID
64 account.verifyEmail user EMAIL_INVALID
65 account.verifyPhone user
66 auth.acceptLoginToken user
67 auth.bindTempAuthKey both ENCRYPTED_MESSAGE_INVALID INPUT_REQUEST_TOO_LONG TEMP_AUTH_KEY_EMPTY Timeout
68 auth.cancelCode user PHONE_NUMBER_INVALID
69 auth.checkPassword user PASSWORD_HASH_INVALID
70 auth.dropTempAuthKeys both
71 auth.exportAuthorization both DC_ID_INVALID
72 auth.exportLoginToken user
73 auth.importAuthorization both AUTH_BYTES_INVALID USER_ID_INVALID
74 auth.importBotAuthorization both ACCESS_TOKEN_EXPIRED ACCESS_TOKEN_INVALID API_ID_INVALID
75 auth.importLoginToken user AUTH_TOKEN_ALREADY_ACCEPTED AUTH_TOKEN_EXPIRED AUTH_TOKEN_INVALID
76 auth.logOut both
77 auth.recoverPassword user CODE_EMPTY
78 auth.requestPasswordRecovery user PASSWORD_EMPTY
83 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
84 bots.answerWebhookJSONQuery bot QUERY_ID_INVALID USER_BOT_INVALID
85 bots.sendCustomRequest bot USER_BOT_INVALID
86 bots.setBotCommands bot BOT_COMMAND_DESCRIPTION_INVALID
87 channels.checkUsername user CHANNEL_INVALID CHAT_ID_INVALID USERNAME_INVALID
88 channels.createChannel user CHAT_TITLE_EMPTY USER_RESTRICTED
89 channels.deleteChannel user CHANNEL_INVALID CHANNEL_PRIVATE
92 channels.deleteUserHistory user CHANNEL_INVALID CHAT_ADMIN_REQUIRED
93 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
94 channels.editBanned both CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ADMIN_INVALID USER_ID_INVALID
95 channels.editCreator user PASSWORD_MISSING PASSWORD_TOO_FRESH_X SESSION_TOO_FRESH_X
96 channels.editLocation user
97 channels.editPhoto both CHANNEL_INVALID CHAT_ADMIN_REQUIRED PHOTO_INVALID
98 channels.editTitle both CHANNEL_INVALID CHAT_ADMIN_REQUIRED CHAT_NOT_MODIFIED
99 channels.exportMessageLink user CHANNEL_INVALID
101 channels.getAdminedPublicChannels user
102 channels.getChannels both CHANNEL_INVALID CHANNEL_PRIVATE NEED_CHAT_INVALID
103 channels.getFullChannel both CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA Timeout
104 channels.getGroupsForDiscussion user
105 channels.getInactiveChannels user
106 channels.getLeftChannels user
107 channels.getMessages both CHANNEL_INVALID CHANNEL_PRIVATE MESSAGE_IDS_EMPTY
108 channels.getParticipant both CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED USER_ID_INVALID USER_NOT_PARTICIPANT
119 channels.toggleSignatures user CHANNEL_INVALID
120 channels.toggleSlowMode user SECONDS_INVALID
121 channels.updateUsername user CHANNELS_ADMIN_PUBLIC_TOO_MUCH CHANNEL_INVALID CHAT_ADMIN_REQUIRED USERNAME_INVALID USERNAME_OCCUPIED
122 contacts.acceptContact user
123 contacts.addContact user CONTACT_NAME_EMPTY
124 contacts.block user CONTACT_ID_INVALID
125 contacts.deleteByPhones user
contacts.deleteContact user CONTACT_ID_INVALID
126 contacts.deleteContacts user NEED_MEMBER_INVALID Timeout
127 contacts.getBlocked user
128 contacts.getContactIDs user
129 contacts.getContacts user
130 contacts.getLocated user
131 contacts.getSaved user TAKEOUT_REQUIRED
132 contacts.getStatuses user
133 contacts.getTopPeers user TYPES_EMPTY
138 contacts.search user QUERY_TOO_SHORT SEARCH_QUERY_EMPTY Timeout
139 contacts.toggleTopPeers user
140 contacts.unblock user CONTACT_ID_INVALID
contest.saveDeveloperInfo both
141 folders.deleteFolder user FOLDER_ID_EMPTY
142 folders.editPeerFolders user FOLDER_ID_INVALID
143 getFutureSalts both
144 help.acceptTermsOfService user
145 help.editUserInfo user USER_INVALID
146 help.getAppChangelog user
173 langpack.getLanguages user LANG_PACK_INVALID
174 langpack.getStrings user LANG_PACK_INVALID
175 messages.acceptEncryption user CHAT_ID_INVALID ENCRYPTION_ALREADY_ACCEPTED ENCRYPTION_ALREADY_DECLINED ENCRYPTION_OCCUPY_FAILED
176 messages.acceptUrlAuth user
177 messages.addChatUser user CHAT_ADMIN_REQUIRED CHAT_ID_INVALID INPUT_USER_DEACTIVATED PEER_ID_INVALID USERS_TOO_MUCH USER_ALREADY_PARTICIPANT USER_ID_INVALID USER_NOT_MUTUAL_CONTACT USER_PRIVACY_RESTRICTED
178 messages.checkChatInvite user INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID
179 messages.clearAllDrafts user
182 messages.deleteChatUser both CHAT_ID_INVALID PEER_ID_INVALID USER_NOT_PARTICIPANT
183 messages.deleteHistory user PEER_ID_INVALID
184 messages.deleteMessages both MESSAGE_DELETE_FORBIDDEN
185 messages.deleteScheduledMessages user
186 messages.discardEncryption user CHAT_ID_EMPTY ENCRYPTION_ALREADY_DECLINED ENCRYPTION_ID_INVALID
187 messages.editChatAbout both
188 messages.editChatAdmin user CHAT_ID_INVALID
190 messages.editChatPhoto both CHAT_ID_INVALID INPUT_CONSTRUCTOR_INVALID INPUT_FETCH_FAIL PEER_ID_INVALID PHOTO_EXT_INVALID
191 messages.editChatTitle both CHAT_ID_INVALID NEED_CHAT_INVALID
192 messages.editInlineBotMessage both MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED
193 messages.editMessage both CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID CHANNEL_INVALID CHANNEL_PRIVATE CHAT_WRITE_FORBIDDEN INLINE_BOT_REQUIRED INPUT_USER_DEACTIVATED MEDIA_NEW_INVALID MEDIA_PREV_INVALID MESSAGE_AUTHOR_REQUIRED MESSAGE_EDIT_TIME_EXPIRED MESSAGE_EMPTY MESSAGE_ID_INVALID MESSAGE_NOT_MODIFIED PEER_ID_INVALID
194 messages.exportChatInvite user both CHAT_ID_INVALID
195 messages.faveSticker user STICKER_ID_INVALID
196 messages.forwardMessages both BROADCAST_PUBLIC_VOTERS_FORBIDDEN CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ADMIN_REQUIRED CHAT_ID_INVALID CHAT_SEND_GIFS_FORBIDDEN CHAT_SEND_MEDIA_FORBIDDEN CHAT_SEND_STICKERS_FORBIDDEN CHAT_WRITE_FORBIDDEN GROUPED_MEDIA_INVALID INPUT_USER_DEACTIVATED MEDIA_EMPTY MESSAGE_IDS_EMPTY MESSAGE_ID_INVALID PEER_ID_INVALID PTS_CHANGE_EMPTY RANDOM_ID_DUPLICATE RANDOM_ID_INVALID SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER
197 messages.getAllChats user
199 messages.getAllStickers user
200 messages.getArchivedStickers user
201 messages.getAttachedStickers user
202 messages.getBotCallbackAnswer user CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout BOT_RESPONSE_TIMEOUT CHANNEL_INVALID DATA_INVALID MESSAGE_ID_INVALID PEER_ID_INVALID Timeout
203 messages.getChats both CHAT_ID_INVALID PEER_ID_INVALID
204 messages.getCommonChats user USER_ID_INVALID
205 messages.getDhConfig user RANDOM_LENGTH_INVALID
206 messages.getDialogFilters user
207 messages.getDialogUnreadMarks user
208 messages.getDialogs user INPUT_CONSTRUCTOR_INVALID OFFSET_PEER_ID_INVALID SESSION_PASSWORD_NEEDED Timeout
209 messages.getDocumentByHash both SHA256_HASH_INVALID
210 messages.getEmojiKeywords user
211 messages.getEmojiKeywordsDifference user
212 messages.getEmojiKeywordsLanguages user
213 messages.getEmojiURL user
214 messages.getFavedStickers user
215 messages.getFeaturedStickers user
216 messages.getFullChat both CHAT_ID_INVALID PEER_ID_INVALID
227 messages.getPeerSettings user CHANNEL_INVALID PEER_ID_INVALID
228 messages.getPinnedDialogs user
229 messages.getPollResults user
230 messages.getPollVotes user BROADCAST_FORBIDDEN
231 messages.getRecentLocations user
232 messages.getRecentStickers user
233 messages.getSavedGifs user
234 messages.getScheduledHistory user
235 messages.getScheduledMessages user
236 messages.getSearchCounters user
237 messages.getSplitRanges user
238 messages.getStatsURL user
239 messages.getStickerSet both STICKERSET_INVALID
240 messages.getStickers user EMOTICON_EMPTY
241 messages.getSuggestedDialogFilters user
242 messages.getUnreadMentions user PEER_ID_INVALID
243 messages.getWebPage user WC_CONVERT_URL_INVALID
244 messages.getWebPagePreview user
245 messages.hideReportSpam messages.hidePeerSettingsBar user PEER_ID_INVALID
246 messages.importChatInvite user CHANNELS_TOO_MUCH INVITE_HASH_EMPTY INVITE_HASH_EXPIRED INVITE_HASH_INVALID SESSION_PASSWORD_NEEDED USERS_TOO_MUCH USER_ALREADY_PARTICIPANT
247 messages.installStickerSet user STICKERSET_INVALID
248 messages.markDialogUnread user
260 messages.reportEncryptedSpam user CHAT_ID_INVALID
261 messages.reportSpam user PEER_ID_INVALID
262 messages.requestEncryption user DH_G_A_INVALID USER_ID_INVALID
263 messages.requestUrlAuth user
264 messages.saveDraft user PEER_ID_INVALID
265 messages.saveGif user GIF_ID_INVALID
266 messages.saveRecentSticker user STICKER_ID_INVALID
267 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
268 messages.searchGifs user SEARCH_QUERY_EMPTY METHOD_INVALID SEARCH_QUERY_EMPTY
269 messages.searchGlobal user SEARCH_QUERY_EMPTY
270 messages.searchStickerSets user
271 messages.sendEncrypted user CHAT_ID_INVALID DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED
272 messages.sendEncryptedFile user MSG_WAIT_FAILED
273 messages.sendEncryptedService user DATA_INVALID ENCRYPTION_DECLINED MSG_WAIT_FAILED USER_IS_BLOCKED
274 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
275 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 EXTERNAL_URL_INVALID FILE_PARTS_INVALID FILE_PART_LENGTH_INVALID 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 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
276 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_TOO_MUCH Timeout USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT YOU_BLOCKED_USER 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
277 messages.sendMultiMedia both SCHEDULE_DATE_TOO_LATE SCHEDULE_TOO_MUCH
278 messages.sendReaction messages.sendScheduledMessages User user REACTION_INVALID
279 messages.sendVote user MESSAGE_POLL_CLOSED OPTION_INVALID
280 messages.setBotCallbackAnswer both QUERY_ID_INVALID URL_INVALID
281 messages.setBotPrecheckoutResults both ERROR_TEXT_EMPTY
282 messages.setBotShippingResults both QUERY_ID_INVALID
283 messages.setEncryptedTyping user CHAT_ID_INVALID
284 messages.setGameScore bot PEER_ID_INVALID USER_BOT_REQUIRED
285 messages.setInlineBotResults bot ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID ARTICLE_TITLE_EMPTY BUTTON_DATA_INVALID BUTTON_TYPE_INVALID BUTTON_URL_INVALID MESSAGE_EMPTY PHOTO_CONTENT_URL_EMPTY PHOTO_THUMB_URL_EMPTY QUERY_ID_INVALID REPLY_MARKUP_INVALID RESULT_TYPE_INVALID SEND_MESSAGE_MEDIA_INVALID SEND_MESSAGE_TYPE_INVALID START_PARAM_INVALID USER_BOT_INVALID WEBDOCUMENT_URL_INVALID
286 messages.setInlineGameScore bot MESSAGE_ID_INVALID USER_BOT_REQUIRED
287 messages.setTyping both CHANNEL_INVALID CHANNEL_PRIVATE CHAT_ID_INVALID CHAT_WRITE_FORBIDDEN PEER_ID_INVALID USER_BANNED_IN_CHANNEL USER_IS_BLOCKED USER_IS_BOT
288 messages.startBot user BOT_INVALID PEER_ID_INVALID START_PARAM_EMPTY START_PARAM_INVALID
289 messages.toggleDialogPin user PEER_ID_INVALID
290 messages.toggleStickerSets user
291 messages.uninstallStickerSet user STICKERSET_INVALID
292 messages.updateDialogFilter user
293 messages.updateDialogFiltersOrder user
294 messages.updatePinnedMessage both
295 messages.uploadEncryptedFile user
296 messages.uploadMedia both BOT_MISSING MEDIA_INVALID PEER_ID_INVALID
297 payments.clearSavedInfo user
298 payments.getBankCardData user
299 payments.getPaymentForm user MESSAGE_ID_INVALID
300 payments.getPaymentReceipt user MESSAGE_ID_INVALID
301 payments.getSavedInfo user
312 photos.deletePhotos user
313 photos.getUserPhotos both MAX_ID_INVALID USER_ID_INVALID
314 photos.updateProfilePhoto user
315 photos.uploadProfilePhoto user FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID FILE_PARTS_INVALID IMAGE_PROCESS_FAILED PHOTO_CROP_SIZE_SMALL PHOTO_EXT_INVALID VIDEO_FILE_INVALID
316 ping both
317 reqDHParams both
318 reqPq both
319 reqPqMulti both
320 rpcDropAnswer both
321 setClientDHParams both
322 stats.getBroadcastStats user BROADCAST_REQUIRED CHAT_ADMIN_REQUIRED STATS_MIGRATE_X
323 stats.getMegagroupStats user CHAT_ADMIN_REQUIRED MEGAGROUP_REQUIRED STATS_MIGRATE_X
324 stats.loadAsyncGraph user
325 stickers.addStickerToSet bot BOT_MISSING STICKERSET_INVALID
326 stickers.changeStickerPosition bot BOT_MISSING STICKER_INVALID
327 stickers.createStickerSet bot BOT_MISSING PACK_SHORT_NAME_INVALID PACK_SHORT_NAME_OCCUPIED PEER_ID_INVALID SHORTNAME_OCCUPY_FAILED STICKERS_EMPTY STICKER_EMOJI_INVALID STICKER_FILE_INVALID STICKER_PNG_DIMENSIONS STICKER_PNG_NOPNG USER_ID_INVALID
328 stickers.removeStickerFromSet bot BOT_MISSING STICKER_INVALID
329 stickers.setStickerSetThumb bot
330 updates.getChannelDifference both CHANNEL_INVALID CHANNEL_PRIVATE CHANNEL_PUBLIC_GROUP_NA HISTORY_GET_FAILED PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID PERSISTENT_TIMESTAMP_OUTDATED RANGES_INVALID Timeout
331 updates.getDifference both AUTH_KEY_PERM_EMPTY CDN_METHOD_INVALID DATE_EMPTY NEED_MEMBER_INVALID PERSISTENT_TIMESTAMP_EMPTY PERSISTENT_TIMESTAMP_INVALID SESSION_PASSWORD_NEEDED STORE_INVALID_SCALAR_TYPE Timeout
332 updates.getState both AUTH_KEY_DUPLICATED MSGID_DECREASE_RETRY SESSION_PASSWORD_NEEDED Timeout

View File

@ -330,7 +330,7 @@ def _write_to_dict(tlobject, builder):
def _write_to_bytes(tlobject, builder): def _write_to_bytes(tlobject, builder):
builder.writeln('def __bytes__(self):') builder.writeln('def _bytes(self):')
# Some objects require more than one flag parameter to be set # Some objects require more than one flag parameter to be set
# at the same time. In this case, add an assertion. # at the same time. In this case, add an assertion.
@ -509,7 +509,7 @@ def _write_arg_to_bytes(builder, arg, args, name=None):
else: else:
# Else it may be a custom type # Else it may be a custom type
builder.write('bytes({})', name) builder.write('{}._bytes()', name)
# If the type is not boxed (i.e. starts with lowercase) we should # If the type is not boxed (i.e. starts with lowercase) we should
# not serialize the constructor ID (so remove its first 4 bytes). # not serialize the constructor ID (so remove its first 4 bytes).

View File

@ -36,7 +36,8 @@ KNOWN_NAMED_EXAMPLES = {
('lang_pack', 'string'): "''", ('lang_pack', 'string'): "''",
('lang_code', 'string'): "'en'", ('lang_code', 'string'): "'en'",
('chat_id', 'int'): '478614198', ('chat_id', 'int'): '478614198',
('client_id', 'long'): 'random.randrange(-2**63, 2**63)' ('client_id', 'long'): 'random.randrange(-2**63, 2**63)',
('video', 'InputFile'): "client.upload_file('/path/to/file.mp4')",
} }
KNOWN_TYPED_EXAMPLES = { KNOWN_TYPED_EXAMPLES = {

View File

View File

View File

@ -0,0 +1,67 @@
import pytest
from telethon import TelegramClient, events, types, utils
def get_client():
return TelegramClient(None, 1, '1')
def get_user_456():
return types.User(
id=456,
access_hash=789,
first_name='User 123'
)
@pytest.mark.asyncio
async def test_get_input_users_no_action_message_no_entities():
event = events.ChatAction.build(types.UpdateChatParticipantDelete(
chat_id=123,
user_id=456,
version=1
))
event._set_client(get_client())
assert await event.get_input_users() == []
@pytest.mark.asyncio
async def test_get_input_users_no_action_message():
user = get_user_456()
event = events.ChatAction.build(types.UpdateChatParticipantDelete(
chat_id=123,
user_id=456,
version=1
))
event._set_client(get_client())
event._entities[user.id] = user
assert await event.get_input_users() == [utils.get_input_peer(user)]
@pytest.mark.asyncio
async def test_get_users_no_action_message_no_entities():
event = events.ChatAction.build(types.UpdateChatParticipantDelete(
chat_id=123,
user_id=456,
version=1
))
event._set_client(get_client())
assert await event.get_users() == []
@pytest.mark.asyncio
async def test_get_users_no_action_message():
user = get_user_456()
event = events.ChatAction.build(types.UpdateChatParticipantDelete(
chat_id=123,
user_id=456,
version=1
))
event._set_client(get_client())
event._entities[user.id] = user
assert await event.get_users() == [user]

View File

View File

@ -1,6 +1,8 @@
import io import io
import pathlib import pathlib
import pytest
from telethon import utils from telethon import utils
from telethon.tl.types import ( from telethon.tl.types import (
MessageMediaGame, Game, PhotoEmpty MessageMediaGame, Game, PhotoEmpty

View File

View File

@ -0,0 +1,13 @@
import pytest
from telethon.tl import types, functions
def test_nested_invalid_serialization():
large_long = 2**62
request = functions.account.SetPrivacyRequest(
key=types.InputPrivacyKeyChatInvite(),
rules=[types.InputPrivacyValueDisallowUsers(users=[large_long])]
)
with pytest.raises(TypeError):
bytes(request)

13
update-docs.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
python setup.py gen docs
rm -rf /tmp/docs
mv docs/ /tmp/docs
git checkout gh-pages
# there's probably better ways but we know none has spaces
rm -rf $(ls /tmp/docs)
mv /tmp/docs/* .
git add constructors/ types/ methods/ index.html js/search.js css/ img/
git commit --amend -m "Update documentation"
git push --force
git checkout master