From 62c65651890efd1c864002bc1f39cac58b753278 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 28 Jun 2018 09:05:00 +0200 Subject: [PATCH] Remove all async/await --- README.rst | 75 +- readthedocs/Makefile | 20 - readthedocs/conf.py | 190 -- readthedocs/custom_roles.py | 69 - .../advanced-usage/accessing-the-full-api.rst | 162 -- readthedocs/extra/advanced-usage/sessions.rst | 134 - .../extra/advanced-usage/update-modes.rst | 65 - readthedocs/extra/basic/asyncio-magic.rst | 321 --- readthedocs/extra/basic/creating-a-client.rst | 228 -- readthedocs/extra/basic/entities.rst | 161 -- readthedocs/extra/basic/getting-started.rst | 93 - readthedocs/extra/basic/installation.rst | 97 - readthedocs/extra/basic/telegram-client.rst | 108 - .../extra/basic/working-with-updates.rst | 273 -- readthedocs/extra/changelog.rst | 2235 ----------------- readthedocs/extra/developing/api-status.rst | 54 - readthedocs/extra/developing/coding-style.rst | 22 - readthedocs/extra/developing/philosophy.rst | 25 - .../extra/developing/project-structure.rst | 52 - .../telegram-api-in-other-languages.rst | 73 - readthedocs/extra/developing/test-servers.rst | 37 - .../tips-for-porting-the-project.rst | 17 - .../understanding-the-type-language.rst | 33 - readthedocs/extra/examples/bots.rst | 71 - .../extra/examples/chats-and-channels.rst | 318 --- .../examples/projects-using-telethon.rst | 44 - readthedocs/extra/examples/users.rst | 72 - .../extra/examples/working-with-messages.rst | 138 - ...eleted-limited-or-deactivated-accounts.rst | 27 - .../extra/troubleshooting/enable-logging.rst | 40 - .../extra/troubleshooting/rpc-errors.rst | 29 - readthedocs/extra/wall-of-shame.rst | 63 - readthedocs/index.rst | 135 - readthedocs/make.bat | 36 - readthedocs/modules.rst | 7 - readthedocs/requirements.txt | 1 - readthedocs/telethon.client.rst | 81 - readthedocs/telethon.errors.rst | 22 - readthedocs/telethon.events.rst | 60 - readthedocs/telethon.extensions.rst | 35 - readthedocs/telethon.network.rst | 35 - readthedocs/telethon.rst | 81 - readthedocs/telethon.tl.custom.rst | 47 - readthedocs/telethon.tl.rst | 16 - setup.py | 5 +- telethon/client/auth.py | 70 +- telethon/client/chats.py | 22 +- telethon/client/dialogs.py | 22 +- telethon/client/downloads.py | 36 +- telethon/client/messageparse.py | 10 +- telethon/client/messages.py | 80 +- telethon/client/telegrambaseclient.py | 46 +- telethon/client/updates.py | 36 +- telethon/client/uploads.py | 36 +- telethon/client/users.py | 50 +- telethon/crypto/cdndecrypter.py | 6 +- telethon/events/chataction.py | 48 +- telethon/events/common.py | 24 +- telethon/events/messageread.py | 6 +- telethon/events/newmessage.py | 8 +- telethon/events/raw.py | 2 +- telethon/events/userupdate.py | 8 +- telethon/extensions/tcpclient.py | 16 +- telethon/network/authenticator.py | 8 +- telethon/network/connection/common.py | 8 +- telethon/network/connection/tcpabridged.py | 18 +- telethon/network/connection/tcpfull.py | 16 +- .../network/connection/tcpintermediate.py | 14 +- telethon/network/connection/tcpobfuscated.py | 6 +- telethon/network/mtprotoplainsender.py | 6 +- telethon/network/mtprotosender.py | 98 +- telethon/sync.py | 81 +- telethon/tl/custom/dialog.py | 4 +- telethon/tl/custom/draft.py | 20 +- telethon/tl/custom/forward.py | 16 +- telethon/tl/custom/message.py | 90 +- telethon/tl/custom/messagebutton.py | 8 +- telethon/tl/tlobject.py | 2 +- telethon_examples/assistant.py | 218 -- .../interactive_telegram_client.py | 382 --- telethon_examples/print_updates.py | 36 - telethon_examples/replier.py | 81 - telethon_generator/generators/tlobject.py | 10 +- telethon_tests/__init__.py | 5 - telethon_tests/test_crypto.py | 143 -- telethon_tests/test_higher_level.py | 49 - telethon_tests/test_network.py | 44 - telethon_tests/test_parser.py | 8 - telethon_tests/test_tl.py | 8 - telethon_tests/test_utils.py | 66 - try_telethon.py | 53 - 91 files changed, 438 insertions(+), 7493 deletions(-) delete mode 100644 readthedocs/Makefile delete mode 100644 readthedocs/conf.py delete mode 100644 readthedocs/custom_roles.py delete mode 100644 readthedocs/extra/advanced-usage/accessing-the-full-api.rst delete mode 100644 readthedocs/extra/advanced-usage/sessions.rst delete mode 100644 readthedocs/extra/advanced-usage/update-modes.rst delete mode 100644 readthedocs/extra/basic/asyncio-magic.rst delete mode 100644 readthedocs/extra/basic/creating-a-client.rst delete mode 100644 readthedocs/extra/basic/entities.rst delete mode 100644 readthedocs/extra/basic/getting-started.rst delete mode 100644 readthedocs/extra/basic/installation.rst delete mode 100644 readthedocs/extra/basic/telegram-client.rst delete mode 100644 readthedocs/extra/basic/working-with-updates.rst delete mode 100644 readthedocs/extra/changelog.rst delete mode 100644 readthedocs/extra/developing/api-status.rst delete mode 100644 readthedocs/extra/developing/coding-style.rst delete mode 100644 readthedocs/extra/developing/philosophy.rst delete mode 100644 readthedocs/extra/developing/project-structure.rst delete mode 100644 readthedocs/extra/developing/telegram-api-in-other-languages.rst delete mode 100644 readthedocs/extra/developing/test-servers.rst delete mode 100644 readthedocs/extra/developing/tips-for-porting-the-project.rst delete mode 100644 readthedocs/extra/developing/understanding-the-type-language.rst delete mode 100644 readthedocs/extra/examples/bots.rst delete mode 100644 readthedocs/extra/examples/chats-and-channels.rst delete mode 100644 readthedocs/extra/examples/projects-using-telethon.rst delete mode 100644 readthedocs/extra/examples/users.rst delete mode 100644 readthedocs/extra/examples/working-with-messages.rst delete mode 100644 readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst delete mode 100644 readthedocs/extra/troubleshooting/enable-logging.rst delete mode 100644 readthedocs/extra/troubleshooting/rpc-errors.rst delete mode 100644 readthedocs/extra/wall-of-shame.rst delete mode 100644 readthedocs/index.rst delete mode 100644 readthedocs/make.bat delete mode 100644 readthedocs/modules.rst delete mode 100644 readthedocs/requirements.txt delete mode 100644 readthedocs/telethon.client.rst delete mode 100644 readthedocs/telethon.errors.rst delete mode 100644 readthedocs/telethon.events.rst delete mode 100644 readthedocs/telethon.extensions.rst delete mode 100644 readthedocs/telethon.network.rst delete mode 100644 readthedocs/telethon.rst delete mode 100644 readthedocs/telethon.tl.custom.rst delete mode 100644 readthedocs/telethon.tl.rst delete mode 100644 telethon_examples/assistant.py delete mode 100644 telethon_examples/interactive_telegram_client.py delete mode 100755 telethon_examples/print_updates.py delete mode 100755 telethon_examples/replier.py delete mode 100644 telethon_tests/__init__.py delete mode 100644 telethon_tests/test_crypto.py delete mode 100644 telethon_tests/test_higher_level.py delete mode 100644 telethon_tests/test_network.py delete mode 100644 telethon_tests/test_parser.py delete mode 100644 telethon_tests/test_tl.py delete mode 100644 telethon_tests/test_utils.py delete mode 100755 try_telethon.py diff --git a/README.rst b/README.rst index 60fb0e74..362bbf7f 100755 --- a/README.rst +++ b/README.rst @@ -4,70 +4,13 @@ Telethon ⭐️ Thanks **everyone** who has starred the project, it means a lot! -|logo| **Telethon** is an `asyncio -`_ **Python 3** library -to interact with Telegram's API. +This is the threaded, simpler version of Telethon for people who +can't bother learning ``asyncio`` but wouldn't like their scripts +to just stop working. This version is also compatible with Python +3.4 but doesn't have any of the benefits of the ``asyncio`` version +and will receive updates slower. -**If you're upgrading from Telethon pre-1.0 to 1.0, please make sure to read** -`this section of the documentation -`_. - -What is this? -------------- - -Telegram is a popular messaging application. This library is meant -to make it easy for you to write Python programs that can interact -with Telegram. Think of it as a wrapper that has already done the -heavy job for you, so you can focus on developing an application. - - -Installing ----------- - -.. code:: sh - - pip3 install telethon - - -Creating a client ------------------ - -.. code:: python - - from telethon import TelegramClient, sync - - # These example values won't work. You must get your own api_id and - # api_hash from https://my.telegram.org, under API Development. - api_id = 12345 - api_hash = '0123456789abcdef0123456789abcdef' - - client = TelegramClient('session_name', api_id, api_hash) - client.start() - - -Doing stuff ------------ - -.. code:: python - - print(client.get_me().stringify()) - - client.send_message('username', 'Hello! Talking to you from Telethon') - client.send_file('username', '/home/myself/Pictures/holidays.jpg') - - client.download_profile_photo('me') - messages = client.get_messages('username') - messages[0].download_media() - - -Next steps ----------- - -Do you like how Telethon looks? Check out `Read The Docs -`_ for a more in-depth explanation, -with examples, troubleshooting issues, and more useful information. - - -.. |logo| image:: logo.svg - :width: 24pt - :height: 24pt +Please consider learning ``asyncio``. The `documentation +`_ is the same for both versions +of the library. Simply don't write any keywords like ``async`` +or ``await`` and you will be good. diff --git a/readthedocs/Makefile b/readthedocs/Makefile deleted file mode 100644 index fd6e0d0a..00000000 --- a/readthedocs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = Telethon -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/readthedocs/conf.py b/readthedocs/conf.py deleted file mode 100644 index 6c8ed0d2..00000000 --- a/readthedocs/conf.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Telethon documentation build configuration file, created by -# sphinx-quickstart on Fri Nov 17 15:36:11 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import re -import os -import sys -sys.path.insert(0, os.path.abspath(os.curdir)) -sys.path.insert(0, os.path.abspath(os.pardir)) - - -root = os.path.abspath(os.path.join(__file__, os.path.pardir, os.path.pardir)) - -tl_ref_url = 'https://lonamiwebs.github.io/Telethon' - - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'custom_roles' -] - -# Change the default role so we can avoid prefixing everything with :obj: -default_role = "py:obj" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = 'Telethon' -copyright = '2017, Lonami' -author = 'Lonami' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -with open(os.path.join(root, 'telethon', 'version.py')) as f: - version = re.search(r"^__version__\s+=\s+'(.*)'$", - f.read(), flags=re.MULTILINE).group(1) - -# The full version, including alpha/beta/rc tags. -release = version - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -html_theme_options = { - 'collapse_navigation': True, - 'display_version': True, - 'navigation_depth': 3, -} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -html_sidebars = { - '**': [ - 'globaltoc.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - ] -} - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = 'Telethondoc' - - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'Telethon.tex', 'Telethon Documentation', - author, 'manual'), -] - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'telethon', 'Telethon Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'Telethon', 'Telethon Documentation', - author, 'Telethon', 'One line description of project.', - 'Miscellaneous'), -] - - - diff --git a/readthedocs/custom_roles.py b/readthedocs/custom_roles.py deleted file mode 100644 index 89a5bd79..00000000 --- a/readthedocs/custom_roles.py +++ /dev/null @@ -1,69 +0,0 @@ -from docutils import nodes, utils -from docutils.parsers.rst.roles import set_classes - - -def make_link_node(rawtext, app, name, options): - """ - Create a link to the TL reference. - - :param rawtext: Text being replaced with link node. - :param app: Sphinx application context - :param name: Name of the object to link to - :param options: Options dictionary passed to role func. - """ - try: - base = app.config.tl_ref_url - if not base: - raise AttributeError - except AttributeError as e: - raise ValueError('tl_ref_url config value is not set') from e - - if base[-1] != '/': - base += '/' - - set_classes(options) - node = nodes.reference(rawtext, utils.unescape(name), - refuri='{}?q={}'.format(base, name), - **options) - return node - - -def tl_role(name, rawtext, text, lineno, inliner, options=None, content=None): - """ - Link to the TL reference. - - Returns 2 part tuple containing list of nodes to insert into the - document and a list of system messages. Both are allowed to be empty. - - :param name: The role name used in the document. - :param rawtext: The entire markup snippet, with role. - :param text: The text marked with the role. - :param lineno: The line number where rawtext appears in the input. - :param inliner: The inliner instance that called us. - :param options: Directive options for customization. - :param content: The directive content for customization. - """ - if options is None: - options = {} - if content is None: - content = [] - - # TODO Report error on type not found? - # Usage: - # msg = inliner.reporter.error(..., line=lineno) - # return [inliner.problematic(rawtext, rawtext, msg)], [msg] - app = inliner.document.settings.env.app - node = make_link_node(rawtext, app, text, options) - return [node], [] - - -def setup(app): - """ - Install the plugin. - - :param app: Sphinx application context. - """ - app.info('Initializing TL reference plugin') - app.add_role('tl', tl_role) - app.add_config_value('tl_ref_url', None, 'env') - return diff --git a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst b/readthedocs/extra/advanced-usage/accessing-the-full-api.rst deleted file mode 100644 index 6a2e87f5..00000000 --- a/readthedocs/extra/advanced-usage/accessing-the-full-api.rst +++ /dev/null @@ -1,162 +0,0 @@ -.. _accessing-the-full-api: - -====================== -Accessing the Full API -====================== - -.. important:: - - While you have access to this, you should always use the friendly - methods listed on :ref:`telethon-client` unless you have a better - reason not to, like a method not existing or you wanting more control. - - -The :ref:`TelegramClient ` doesn't offer a method for -every single request the Telegram API supports. However, it's very simple to -*call* or *invoke* any request. Whenever you need something, don't forget to -`check the documentation`__ and look for the `method you need`__. There you -can go through a sorted list of everything you can do. - - -.. note:: - - The reason to keep both https://lonamiwebs.github.io/Telethon and this - documentation alive is that the former allows instant search results - as you type, and a "Copy import" button. If you like namespaces, you - can also do ``from telethon.tl import types, functions``. Both work. - - -.. important:: - - All the examples in this documentation assume that you have - ``from telethon import sync`` or ``import telethon.sync`` - for the sake of simplicity and that you understand what - it does (see :ref:`asyncio-magic` for more). Simply add - either line at the beginning of your project and it will work. - - -You should also refer to the documentation to see what the objects -(constructors) Telegram returns look like. Every constructor inherits -from a common type, and that's the reason for this distinction. - -Say `client.send_message -` didn't exist, -we could use the `search`__ to look for "message". There we would find -:tl:`SendMessageRequest`, which we can work with. - -Every request is a Python class, and has the parameters needed for you -to invoke it. You can also call ``help(request)`` for information on -what input parameters it takes. Remember to "Copy import to the -clipboard", or your script won't be aware of this class! Now we have: - -.. code-block:: python - - from telethon.tl.functions.messages import SendMessageRequest - -If you're going to use a lot of these, you may do: - -.. code-block:: python - - from telethon.tl import types, functions - # We now have access to 'functions.messages.SendMessageRequest' - -We see that this request must take at least two parameters, a ``peer`` -of type :tl:`InputPeer`, and a ``message`` which is just a Python -``str``\ ing. - -How can we retrieve this :tl:`InputPeer`? We have two options. We manually -construct one, for instance: - -.. code-block:: python - - from telethon.tl.types import InputPeerUser - - peer = InputPeerUser(user_id, user_hash) - -Or we call `client.get_input_entity -`: - -.. code-block:: python - - import telethon.sync - peer = client.get_input_entity('someone') - - -When you're going to invoke an API method, most require you to pass an -:tl:`InputUser`, :tl:`InputChat`, or so on, this is why using -`client.get_input_entity ` -is more straightforward (and often immediate, if you've seen the user before, -know their ID, etc.). If you also **need** to have information about the whole -user, use `client.get_entity ` -instead: - -.. code-block:: python - - entity = client.get_entity('someone') - -In the later case, when you use the entity, the library will cast it to -its "input" version for you. If you already have the complete user and -want to cache its input version so the library doesn't have to do this -every time its used, simply call `telethon.utils.get_input_peer`: - -.. code-block:: python - - from telethon import utils - peer = utils.get_input_peer(entity) - - -.. note:: - - Since ``v0.16.2`` this is further simplified. The ``Request`` itself - will call `client.get_input_entity < - telethon.client.users.UserMethods.get_input_entity>` for you when required, - but it's good to remember what's happening. - - -After this small parenthesis about `client.get_entity -` versus -`client.get_input_entity `, -we have everything we need. To invoke our -request we do: - -.. code-block:: python - - result = client(SendMessageRequest(peer, 'Hello there!')) - # __call__ is an alias for client.invoke(request). Both will work - -Message sent! Of course, this is only an example. There are over 250 -methods available as of layer 80, and you can use every single of them -as you wish. Remember to use the right types! To sum up: - -.. code-block:: python - - result = client(SendMessageRequest( - client.get_input_entity('username'), 'Hello there!' - )) - - -This can further be simplified to: - -.. code-block:: python - - result = client(SendMessageRequest('username', 'Hello there!')) - # Or even - result = client(SendMessageRequest(PeerChannel(id), 'Hello there!')) - -.. note:: - - Note that some requests have a "hash" parameter. This is **not** - your ``api_hash``! It likely isn't your self-user ``.access_hash`` either. - - It's a special hash used by Telegram to only send a difference of new data - that you don't already have with that request, so you can leave it to 0, - and it should work (which means no hash is known yet). - - For those requests having a "limit" parameter, you can often set it to - zero to signify "return default amount". This won't work for all of them - though, for instance, in "messages.search" it will actually return 0 items. - - -__ https://lonamiwebs.github.io/Telethon -__ https://lonamiwebs.github.io/Telethon/methods/index.html -__ https://lonamiwebs.github.io/Telethon/?q=message&redirect=no diff --git a/readthedocs/extra/advanced-usage/sessions.rst b/readthedocs/extra/advanced-usage/sessions.rst deleted file mode 100644 index eda74e7a..00000000 --- a/readthedocs/extra/advanced-usage/sessions.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. _sessions: - -============== -Session Files -============== - -The first parameter you pass to the constructor of the -:ref:`TelegramClient ` is -the ``session``, and defaults to be the session name (or full path). That is, -if you create a ``TelegramClient('anon')`` instance and connect, an -``anon.session`` file will be created in the working directory. - -Note that if you pass a string it will be a file in the current working -directory, although you can also pass absolute paths. - -The session file contains enough information for you to login without -re-sending the code, so if you have to enter the code more than once, -maybe you're changing the working directory, renaming or removing the -file, or using random names. - -These database files using ``sqlite3`` contain the required information to -talk to the Telegram servers, such as to which IP the client should connect, -port, authorization key so that messages can be encrypted, and so on. - -These files will by default also save all the input entities that you've seen, -so that you can get information about an user or channel by just their ID. -Telegram will **not** send their ``access_hash`` required to retrieve more -information about them, if it thinks you have already seem them. For this -reason, the library needs to store this information offline. - -The library will by default too save all the entities (chats and channels -with their name and username, and users with the phone too) in the session -file, so that you can quickly access them by username or phone number. - -If you're not going to work with updates, or don't need to cache the -``access_hash`` associated with the entities' ID, you can disable this -by setting ``client.session.save_entities = False``. - -Custom Session Storage ----------------------- - -If you don't want to use the default SQLite session storage, you can also use -one of the other implementations or implement your own storage. - -To use a custom session storage, simply pass the custom session instance to -:ref:`TelegramClient ` instead of -the session name. - -Telethon contains two implementations of the abstract ``Session`` class: - -* ``MemorySession``: stores session data in Python variables. -* ``SQLiteSession``, (default): stores sessions in their own SQLite databases. - -There are other community-maintained implementations available: - -* `SQLAlchemy `_: stores all sessions in a single database via SQLAlchemy. -* `Redis `_: stores all sessions in a single Redis data store. - -Creating your own storage -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The easiest way to create your own storage implementation is to use ``MemorySession`` -as the base and check out how ``SQLiteSession`` or one of the community-maintained -implementations work. You can find the relevant Python files under the ``sessions`` -directory in Telethon. - -After you have made your own implementation, you can add it to the community-maintained -session implementation list above with a pull request. - -SQLite Sessions and Heroku --------------------------- - -You probably have a newer version of SQLite installed (>= 3.8.2). Heroku uses -SQLite 3.7.9 which does not support ``WITHOUT ROWID``. So, if you generated -your session file on a system with SQLite >= 3.8.2 your session file will not -work on Heroku's platform and will throw a corrupted schema error. - -There are multiple ways to solve this, the easiest of which is generating a -session file on your Heroku dyno itself. The most complicated is creating -a custom buildpack to install SQLite >= 3.8.2. - - -Generating a SQLite Session File on a Heroku Dyno -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. note:: - Due to Heroku's ephemeral filesystem all dynamically generated - files not part of your applications buildpack or codebase are destroyed - upon each restart. - -.. warning:: - Do not restart your application Dyno at any point prior to retrieving your - session file. Constantly creating new session files from Telegram's API - will result in a 24 hour rate limit ban. - -Due to Heroku's ephemeral filesystem all dynamically generated -files not part of your applications buildpack or codebase are destroyed upon -each restart. - -Using this scaffolded code we can start the authentication process: - - .. code-block:: python - - client = TelegramClient('login.session', api_id, api_hash).start() - -At this point your Dyno will crash because you cannot access stdin. Open your -Dyno's control panel on the Heroku website and "Run console" from the "More" -dropdown at the top right. Enter ``bash`` and wait for it to load. - -You will automatically be placed into your applications working directory. -So run your application ``python app.py`` and now you can complete the input -requests such as "what is your phone number" etc. - -Once you're successfully authenticated exit your application script with -CTRL + C and ``ls`` to confirm ``login.session`` exists in your current -directory. Now you can create a git repo on your account and commit -``login.session`` to that repo. - -You cannot ``ssh`` into your Dyno instance because it has crashed, so unless -you programatically upload this file to a server host this is the only way to -get it off of your Dyno. - -You now have a session file compatible with SQLite <= 3.8.2. Now you can -programatically fetch this file from an external host (Firebase, S3 etc.) -and login to your session using the following scaffolded code: - - .. code-block:: python - - fileName, headers = urllib.request.urlretrieve(file_url, 'login.session') - client = TelegramClient(os.path.abspath(fileName), api_id, api_hash).start() - -.. note:: - - ``urlretrieve`` will be depreciated, consider using ``requests``. - - ``file_url`` represents the location of your file. diff --git a/readthedocs/extra/advanced-usage/update-modes.rst b/readthedocs/extra/advanced-usage/update-modes.rst deleted file mode 100644 index c3391499..00000000 --- a/readthedocs/extra/advanced-usage/update-modes.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. _update-modes: - -============ -Update Modes -============ - -With ``asyncio``, the library has several tasks running in the background. -One task is used for sending requests, another task is used to receive them, -and a third one is used to handle updates. - -To handle updates, you must keep your script running. You can do this in -several ways. For instance, if you are *not* running ``asyncio``'s event -loop, you should use `client.run_until_disconnected -`: - -.. code-block:: python - - import asyncio - from telethon import TelegramClient - - client = TelegramClient(...) - ... - client.run_until_disconnected() - - -Behind the scenes, this method is ``await``'ing on the `client.disconnected -` property, -so the code above and the following are equivalent: - - -.. code-block:: python - - import asyncio - from telethon import TelegramClient - - client = TelegramClient(...) - - async def main(): - await client.disconnected - - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - - -You could also run `client.disconnected -` -until it completed. - -But if you don't want to ``await``, then you should know what you want -to be doing instead! What matters is that you shouldn't let your script -die. If you don't care about updates, you don't need any of this. - -Notice that unlike `client.disconnected -`, -`client.run_until_disconnected -` will -handle ``KeyboardInterrupt`` with you. This method is special and can -also be ran while the loop is running, so you can do this: - -.. code-block:: python - - async def main(): - await client.run_until_disconnected() - - loop.run_until_complete(main()) diff --git a/readthedocs/extra/basic/asyncio-magic.rst b/readthedocs/extra/basic/asyncio-magic.rst deleted file mode 100644 index c4129b9e..00000000 --- a/readthedocs/extra/basic/asyncio-magic.rst +++ /dev/null @@ -1,321 +0,0 @@ -.. _asyncio-magic: - -================== -Magic with asyncio -================== - -.. important:: - - TL; DR; If you've upgraded to Telethon 1.0 from a previous version - **and you're not using events or updates**, add this line: - - .. code-block:: python - - import telethon.sync - - At the beginning of your main script and you will be good. If you do use - updates or events, keep reading, or install the latest version using - threads and Python 3.4 support with ``pip install telethon==0.19.1.6``. - - You might also want to check the :ref:`changelog`. - - -The sync module -*************** - -It's time to tell you the truth. The library has been doing magic behind -the scenes. We're sorry to tell you this, but at least it wasn't dark magic! - -You may have noticed one of these lines across the documentation: - -.. code-block:: python - - from telethon import sync - # or - import telethon.sync - -Either of these lines will import the *magic* ``sync`` module. When you -import this module, you can suddenly use all the methods defined in the -:ref:`TelegramClient ` like so: - -.. code-block:: python - - client.send_message('me', 'Hello!') - - for dialog in client.iter_dialogs(): - print(dialog.title) - - -What happened behind the scenes is that all those methods, called *coroutines*, -were rewritten to be normal methods that will block (with some exceptions). -This means you can use the library without worrying about ``asyncio`` and -event loops. - -However, this only works until you run the event loop yourself explicitly: - -.. code-block:: python - - import asyncio - - async def coro(): - client.send_message('me', 'Hello!') # <- no longer works! - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - - -What things will work and when? -******************************* - -You can use all the methods in the :ref:`TelegramClient ` -in a synchronous, blocking way without trouble, as long as you're not running -the loop as we saw above (the ``loop.run_until_complete(...)`` line runs "the -loop"). If you're running the loop, then *you* are the one responsible to -``await`` everything. So to fix the code above: - -.. code-block:: python - - import asyncio - - async def coro(): - await client.send_message('me', 'Hello!') - # ^ notice this new await - - loop = asyncio.get_event_loop() - loop.run_until_complete(coro()) - -The library can only run the loop until the method completes if the loop -isn't already running, which is why the magic can't work if you run the -loop yourself. - -**When you work with updates or events**, the loop needs to be -running one way or another (using `client.run_until_disconnected() -` runs the loop), -so your event handlers must be ``async def``. - -.. important:: - - Turning your event handlers into ``async def`` is the biggest change - between Telethon pre-1.0 and 1.0, but updating will likely cause a - noticeable speed-up in your programs. Keep reading! - - -So in short, you can use **all** methods in the client with ``await`` or -without it if the loop isn't running: - -.. code-block:: python - - client.send_message('me', 'Hello!') # works - - async def main(): - await client.send_message('me', 'Hello!') # also works - - loop.run_until_complete(main()) - - -When you work with updates, you should stick using the ``async def main`` -way, since your event handlers will be ``async def`` too. - -.. note:: - - There are two exceptions. Both `client.run_until_disconnected() - ` and - `client.start() ` work in - and outside of ``async def`` for convenience without importing the - magic module. The rest of methods remain ``async`` unless you import it. - -You can skip the rest if you already know how ``asyncio`` works and you -already understand what the magic does and how it works. Just remember -to ``await`` all your methods if you're inside an ``async def`` or are -using updates and you will be good. - - -Why asyncio? -************ - -Python's `asyncio `_ is the -standard way to run asynchronous code from within Python. Since Python 3.5, -using ``async def`` and ``await`` became possible, and Python 3.6 further -improves what you can do with asynchronous code, although it's not the only -way (other projects like `Trio `_ also exist). - -Telegram is a service where all API calls are executed in an asynchronous -way. You send your request, and eventually, Telegram will process it and -respond to it. It feels natural to make a library that also behaves this -way: you send a request, and you can ``await`` for its result. - -Now that we know that Telegram's API follows an asynchronous model, you -should understand the benefits of developing a library that does the same, -it greatly simplifies the internal code and eases working with the API. - -Using ``asyncio`` keeps a cleaner library that will be easier to understand, -develop, and that will be faster than using threads, which are harder to get -right and can cause issues. It also enables to use the powerful ``asyncio`` -system such as futures, timeouts, cancellation, etc. in a natural way. - -If you're still not convinced or you're just not ready for using ``asyncio``, -the library offers a synchronous interface without the need for all the -``async`` and ``await`` you would otherwise see. `Follow this link -`_ to find out more. - - -How do I get started? -********************* - -To get started with ``asyncio``, all you need is to setup your main -``async def`` like so: - -.. code-block:: python - - import asyncio - - async def main(): - pass # Your code goes here - - if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - -You don't need to ``import telethon.sync`` if you're going to work this -way. This is the best way to work in real programs since the loop won't -be starting and ending all the time, but is a bit more annoying to setup. - -Inside ``async def main()``, you can use the ``await`` keyword. Most -methods in the :ref:`TelegramClient ` are ``async def``. -You must ``await`` all ``async def``, also known as a *coroutines*: - -.. code-block:: python - - async def main(): - client = TelegramClient(...) - - # client.start() is a coroutine (async def), it needs an await - await client.start() - - # Sending a message also interacts with the API, and needs an await - await client.send_message('me', 'Hello myself!') - - -If you don't know anything else about ``asyncio``, this will be enough -to get you started. Once you're ready to learn more about it, you will -be able to use that power and everything you've learnt with Telethon. -Just remember that if you use ``await``, you need to be inside of an -``async def``. - -Another way to use ``async def`` is to use ``loop.run_until_complete(f())``, -but the loop must not be running before. - -If you want to handle updates (and don't let the script die), you must -`await client.run_until_disconnected() -` -which is a property that you can wait on until you call -`await client.disconnect() -`: - - -.. code-block:: python - - client = TelegramClient(...) - - @client.on(events.NewMessage) - async def handler(event): - print(event) - - async def main(): - await client.start() - await client.run_until_disconnected() - - if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - -`client.run_until_disconnected() -` and -`client.start() -` are special-cased and work -inside or outside ``async def`` for convenience, even without importing -the ``sync`` module, so you can also do this: - -.. code-block:: python - - client = TelegramClient(...) - - @client.on(events.NewMessage) - async def handler(event): - print(event) - - if __name__ == '__main__': - client.start() - client.run_until_disconnected() - - -Which methods should I use and when? -************************************ - -Something to note is that you must always get an event loop if you -want to be able to make any API calls. This is done as follows: - -.. code-block:: python - - import asyncio - loop = asyncio.get_event_loop() - -The loop must be running, or things will never get sent. -Normally, you use ``run_until_complete``: - -.. code-block:: python - - async def coroutine(): - await asyncio.sleep(1) - - loop.run_until_complete(coroutine()) - -Note that ``asyncio.sleep`` is in itself a coroutine, so this will -work too: - -.. code-block:: python - - loop.run_until_complete(asyncio.sleep(1)) - -Generally, you make an ``async def main()`` if you need to ``await`` -a lot of things, instead of typing ``run_until_complete`` all the time: - -.. code-block:: python - - async def main(): - message = await client.send_message('me', 'Hi') - await asyncio.sleep(1) - await message.delete() - - loop.run_until_complete(main()) - - # vs - - message = loop.run_until_complete(client.send_message('me', 'Hi')) - loop.run_until_complete(asyncio.sleep(1)) - loop.run_until_complete(message.delete()) - -You can see that the first version has more lines, but you had to type -a lot less. You can also rename the run method to something shorter: - -.. code-block:: python - - # Note no parenthesis (), we're not running it, just copying the method - rc = loop.run_until_complete - message = rc(client.send_message('me', 'Hi')) - rc(asyncio.sleep(1)) - rc(message.delete()) - -The documentation generally runs the loop until complete behind the -scenes if you've imported the magic ``sync`` module, but if you haven't, -you need to run the loop yourself. We recommend that you use the -``async def main()`` method to do all your work with ``await``. -It's the easiest and most performant thing to do. - - -More resources to learn asyncio -******************************* - -If you would like to learn a bit more about why ``asyncio`` is something -you should learn, `check out my blog post -`_ that goes into more detail. diff --git a/readthedocs/extra/basic/creating-a-client.rst b/readthedocs/extra/basic/creating-a-client.rst deleted file mode 100644 index bda905e4..00000000 --- a/readthedocs/extra/basic/creating-a-client.rst +++ /dev/null @@ -1,228 +0,0 @@ -.. _creating-a-client: - -================= -Creating a Client -================= - - -Before working with Telegram's API, you need to get your own API ID and hash: - -1. Follow `this link `_ and login with your - phone number. - -2. Click under API Development tools. - -3. A *Create new application* window will appear. Fill in your application - details. There is no need to enter any *URL*, and only the first two - fields (*App title* and *Short name*) can currently be changed later. - -4. Click on *Create application* at the end. Remember that your - **API hash is secret** and Telegram won't let you revoke it. - Don't post it anywhere! - -Once that's ready, the next step is to create a ``TelegramClient``. -This class will be your main interface with Telegram's API, and creating -one is very simple: - -.. code-block:: python - - from telethon import TelegramClient, sync - - # Use your own values here - api_id = 12345 - api_hash = '0123456789abcdef0123456789abcdef' - - client = TelegramClient('some_name', api_id, api_hash) - - -Note that ``'some_name'`` will be used to save your session (persistent -information such as access key and others) as ``'some_name.session'`` in -your disk. This is by default a database file using Python's ``sqlite3``. - -.. note:: - - It's important that the library always accesses the same session file so - that you don't need to re-send the code over and over again. By default it - creates the file in your working directory, but absolute paths work too. - - -.. important:: - - The process shown here shows how to sign in *manually*. You **should** - use `client.start() ` instead - unless you have a better reason not to (e.g. you need more control): - - .. code-block:: python - - client.start() - - This is explained after going through the manual process. - - -Before using the client, you must be connected to Telegram. -Doing so is very easy: - -.. code-block:: python - - client.connect() - -You may or may not be authorized yet. You must be authorized -before you're able to send any request: - -.. code-block:: python - - client.is_user_authorized() # Returns True if you can send requests - -If you're not authorized, you need to `.sign_in -`: - -.. code-block:: python - - phone_number = '+34600000000' - client.send_code_request(phone_number) - myself = client.sign_in(phone_number, input('Enter code: ')) - # If .sign_in raises PhoneNumberUnoccupiedError, use .sign_up instead - # If .sign_in raises SessionPasswordNeeded error, call .sign_in(password=...) - # You can import both exceptions from telethon.errors. - -.. note:: - - If you send the code that Telegram sent you over the app through the - app itself, it will expire immediately. You can still send the code - through the app by "obfuscating" it (maybe add a magic constant, like - ``12345``, and then subtract it to get the real code back) or any other - technique. - -``myself`` is your Telegram user. You can view all the information about -yourself by doing ``print(myself.stringify())``. You're now ready to use -the client as you wish! Remember that any object returned by the API has -mentioned ``.stringify()`` method, and printing these might prove useful. - -As a full example: - -.. code-block:: python - - client = TelegramClient('anon', api_id, api_hash) - - client.connect() - if not client.is_user_authorized(): - client.send_code_request(phone_number) - me = client.sign_in(phone_number, input('Enter code: ')) - - -All of this, however, can be done through a call to `.start() -`: - -.. code-block:: python - - client = TelegramClient('anon', api_id, api_hash) - client.start() - - -The code shown is just what `.start() -` will be doing behind the scenes -(with a few extra checks), so that you know how to sign in case you want -to avoid using ``input()`` (the default) for whatever reason. If no phone -or bot token is provided, you will be asked one through ``input()``. The -method also accepts a ``phone=`` and ``bot_token`` parameters. - -You can use either, as both will work. Determining which -is just a matter of taste, and how much control you need. - -Remember that you can get yourself at any time with `client.get_me() -`. - -.. warning:: - Please note that if you fail to login around 5 times (or change the first - parameter of the :ref:`TelegramClient `, which is the session - name) you will receive a ``FloodWaitError`` of around 22 hours, so be - careful not to mess this up! This shouldn't happen if you're doing things - as explained, though. - -.. note:: - If you want to use a **proxy**, you have to `install PySocks`__ - (via pip or manual) and then set the appropriated parameters: - - .. code-block:: python - - import socks - client = TelegramClient('session_id', - api_id=12345, api_hash='0123456789abcdef0123456789abcdef', - proxy=(socks.SOCKS5, 'localhost', 4444) - ) - - The ``proxy=`` argument should be a tuple, a list or a dict, - consisting of parameters described `here`__. - - - -Two Factor Authorization (2FA) -****************************** - -If you have Two Factor Authorization (from now on, 2FA) enabled on your -account, calling `.sign_in() -` will raise a -``SessionPasswordNeededError``. When this happens, just use the method -again with a ``password=``: - -.. code-block:: python - - import getpass - from telethon.errors import SessionPasswordNeededError - - client.sign_in(phone) - try: - client.sign_in(code=input('Enter code: ')) - except SessionPasswordNeededError: - client.sign_in(password=getpass.getpass()) - - -The mentioned `.start() -` method will handle this for you as -well, but you must set the ``password=`` parameter beforehand (it won't be -asked). - -If you don't have 2FA enabled, but you would like to do so through the -library, use `client.edit_2fa() -`. - -Be sure to know what you're doing when using this function and -you won't run into any problems. Take note that if you want to -set only the email/hint and leave the current password unchanged, -you need to "redo" the 2fa. - -See the examples below: - -.. code-block:: python - - from telethon.errors import EmailUnconfirmedError - - # Sets 2FA password for first time: - client.edit_2fa(new_password='supersecurepassword') - - # Changes password: - client.edit_2fa(current_password='supersecurepassword', - new_password='changedmymind') - - # Clears current password (i.e. removes 2FA): - client.edit_2fa(current_password='changedmymind', new_password=None) - - # Sets new password with recovery email: - try: - client.edit_2fa(new_password='memes and dreams', - email='JohnSmith@example.com') - # Raises error (you need to check your email to complete 2FA setup.) - except EmailUnconfirmedError: - # You can put email checking code here if desired. - pass - - # Also take note that unless you remove 2FA or explicitly - # give email parameter again it will keep the last used setting - - # Set hint after already setting password: - client.edit_2fa(current_password='memes and dreams', - new_password='memes and dreams', - hint='It keeps you alive') - -__ https://github.com/Anorov/PySocks#installation -__ https://github.com/Anorov/PySocks#usage-1 diff --git a/readthedocs/extra/basic/entities.rst b/readthedocs/extra/basic/entities.rst deleted file mode 100644 index f1578465..00000000 --- a/readthedocs/extra/basic/entities.rst +++ /dev/null @@ -1,161 +0,0 @@ -.. _entities: - -========================= -Users, Chats and Channels -========================= - - -Introduction -************ - -The library widely uses the concept of "entities". An entity will refer -to any :tl:`User`, :tl:`Chat` or :tl:`Channel` object that the API may return -in response to certain methods, such as :tl:`GetUsersRequest`. - -.. note:: - - When something "entity-like" is required, it means that you need to - provide something that can be turned into an entity. These things include, - but are not limited to, usernames, exact titles, IDs, :tl:`Peer` objects, - or even entire :tl:`User`, :tl:`Chat` and :tl:`Channel` objects and even - phone numbers from people you have in your contacts. - - To "encounter" an ID, you would have to "find it" like you would in the - normal app. If the peer is in your dialogs, you would need to - `client.get_dialogs() `. - If the peer is someone in a group, you would similarly - `client.get_participants(group) `. - - Once you have encountered an ID, the library will (by default) have saved - their ``access_hash`` for you, which is needed to invoke most methods. - This is why sometimes you might encounter this error when working with - the library. You should ``except ValueError`` and run code that you know - should work to find the entity. - - -Getting entities -**************** - -Through the use of the :ref:`sessions`, the library will automatically -remember the ID and hash pair, along with some extra information, so -you're able to just do this: - -.. code-block:: python - - # Dialogs are the "conversations you have open". - # This method returns a list of Dialog, which - # has the .entity attribute and other information. - dialogs = client.get_dialogs() - - # All of these work and do the same. - lonami = client.get_entity('lonami') - lonami = client.get_entity('t.me/lonami') - lonami = client.get_entity('https://telegram.dog/lonami') - - # Other kind of entities. - channel = client.get_entity('telegram.me/joinchat/AAAAAEkk2WdoDrB4-Q8-gg') - contact = client.get_entity('+34xxxxxxxxx') - friend = client.get_entity(friend_id) - - # Getting entities through their ID (User, Chat or Channel) - entity = client.get_entity(some_id) - - # You can be more explicit about the type for said ID by wrapping - # it inside a Peer instance. This is recommended but not necessary. - from telethon.tl.types import PeerUser, PeerChat, PeerChannel - - my_user = client.get_entity(PeerUser(some_id)) - my_chat = client.get_entity(PeerChat(some_id)) - my_channel = client.get_entity(PeerChannel(some_id)) - - -All methods in the :ref:`telegram-client` call `.get_input_entity() -` prior -to sending the requst to save you from the hassle of doing so manually. -That way, convenience calls such as `client.send_message('lonami', 'hi!') -` -become possible. - -Every entity the library encounters (in any response to any call) will by -default be cached in the ``.session`` file (an SQLite database), to avoid -performing unnecessary API calls. If the entity cannot be found, additonal -calls like :tl:`ResolveUsernameRequest` or :tl:`GetContactsRequest` may be -made to obtain the required information. - - -Entities vs. Input Entities -*************************** - -.. note:: - - Don't worry if you don't understand this section, just remember some - of the details listed here are important. When you're calling a method, - don't call `client.get_entity() ` - beforehand, just use the username or phone, or the entity retrieved by - other means like `client.get_dialogs() - `. - - -On top of the normal types, the API also make use of what they call their -``Input*`` versions of objects. The input version of an entity (e.g. -:tl:`InputPeerUser`, :tl:`InputChat`, etc.) only contains the minimum -information that's required from Telegram to be able to identify -who you're referring to: a :tl:`Peer`'s **ID** and **hash**. - -This ID/hash pair is unique per user, so if you use the pair given by another -user **or bot** it will **not** work. - -To save *even more* bandwidth, the API also makes use of the :tl:`Peer` -versions, which just have an ID. This serves to identify them, but -peers alone are not enough to use them. You need to know their hash -before you can "use them". - -As we just mentioned, API calls don't need to know the whole information -about the entities, only their ID and hash. For this reason, another method, -`client.get_input_entity() ` -is available. This will always use the cache while possible, making zero API -calls most of the time. When a request is made, if you provided the full -entity, e.g. an :tl:`User`, the library will convert it to the required -:tl:`InputPeer` automatically for you. - -**You should always favour** -`client.get_input_entity() ` -**over** -`client.get_entity() ` -for this reason! Calling the latter will always make an API call to get -the most recent information about said entity, but invoking requests don't -need this information, just the :tl:`InputPeer`. Only use -`client.get_entity() ` -if you need to get actual information, like the username, name, title, etc. -of the entity. - -To further simplify the workflow, since the version ``0.16.2`` of the -library, the raw requests you make to the API are also able to call -`client.get_input_entity() ` -wherever needed, so you can even do things like: - -.. code-block:: python - - client(SendMessageRequest('username', 'hello')) - -The library will call the ``.resolve()`` method of the request, which will -resolve ``'username'`` with the appropriated :tl:`InputPeer`. Don't worry if -you don't get this yet, but remember some of the details here are important. - - -Full entities -************* - -In addition to :tl:`PeerUser`, :tl:`InputPeerUser`, :tl:`User` (and its -variants for chats and channels), there is also the concept of :tl:`UserFull`. - -This full variant has additional information such as whether the user is -blocked, its notification settings, the bio or about of the user, etc. - -There is also :tl:`messages.ChatFull` which is the equivalent of full entities -for chats and channels, with also the about section of the channel. Note that -the ``users`` field only contains bots for the channel (so that clients can -suggest commands to use). - -You can get both of these by invoking :tl:`GetFullUser`, :tl:`GetFullChat` -and :tl:`GetFullChannel` respectively. diff --git a/readthedocs/extra/basic/getting-started.rst b/readthedocs/extra/basic/getting-started.rst deleted file mode 100644 index a3dec33d..00000000 --- a/readthedocs/extra/basic/getting-started.rst +++ /dev/null @@ -1,93 +0,0 @@ -.. _getting-started: - - -=============== -Getting Started -=============== - - -Simple Installation -******************* - -.. code-block:: sh - - pip3 install telethon - -**More details**: :ref:`installation` - - -Creating a client -***************** - -.. code-block:: python - - from telethon import TelegramClient, sync - - # These example values won't work. You must get your own api_id and - # api_hash from https://my.telegram.org, under API Development. - api_id = 12345 - api_hash = '0123456789abcdef0123456789abcdef' - - client = TelegramClient('session_name', api_id, api_hash).start() - -**More details**: :ref:`creating-a-client` - - -Basic Usage -*********** - -.. code-block:: python - - # Getting information about yourself - me = client.get_me() - print(me.stringify()) - - # Sending a message (you can use 'me' or 'self' to message yourself) - client.send_message('username', 'Hello World from Telethon!') - - # Sending a file - client.send_file('username', '/home/myself/Pictures/holidays.jpg') - - # Retrieving messages from a chat - from telethon import utils - for message in client.iter_messages('username', limit=10): - print(utils.get_display_name(message.sender), message.message) - - # Listing all the dialogs (conversations you have open) - for dialog in client.get_dialogs(limit=10): - print(dialog.name, dialog.draft.text) - - # Downloading profile photos (default path is the working directory) - client.download_profile_photo('username') - - # Once you have a message with .media (if message.media) - # you can download it using client.download_media(), - # or even using message.download_media(): - messages = client.get_messages('username') - messages[0].download_media() - -**More details**: :ref:`telegram-client` - -See :ref:`telethon-client` for all available friendly methods. - - -Handling Updates -**************** - -.. code-block:: python - - from telethon import events - - @client.on(events.NewMessage(incoming=True, pattern='(?i)hi')) - def handler(event): - event.reply('Hello!') - - client.run_until_disconnected() - -**More details**: :ref:`working-with-updates` - - ----------- - -You can continue by clicking on the "More details" link below each -snippet of code or the "Next" button at the bottom of the page. diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst deleted file mode 100644 index 983472b5..00000000 --- a/readthedocs/extra/basic/installation.rst +++ /dev/null @@ -1,97 +0,0 @@ -.. _installation: - -============ -Installation -============ - - -Automatic Installation -********************** - -To install Telethon, simply do: - -.. code-block:: sh - - pip3 install telethon - -Needless to say, you must have Python 3 and PyPi installed in your system. -See https://python.org and https://pypi.python.org/pypi/pip for more. - -If you already have the library installed, upgrade with: - -.. code-block:: sh - - pip3 install --upgrade telethon - -You can also install the library directly from GitHub or a fork: - -.. code-block:: sh - - # pip3 install git+https://github.com/LonamiWebs/Telethon.git - or - $ git clone https://github.com/LonamiWebs/Telethon.git - $ cd Telethon/ - # pip install -Ue . - -If you don't have root access, simply pass the ``--user`` flag to the pip -command. If you want to install a specific branch, append ``@branch`` to -the end of the first install command. - -By default the library will use a pure Python implementation for encryption, -which can be really slow when uploading or downloading files. If you don't -mind using a C extension, install `cryptg `__ -via ``pip`` or as an extra: - -.. code-block:: sh - - pip3 install telethon[cryptg] - - -Manual Installation -******************* - -1. Install the required ``pyaes`` (`GitHub`__ | `PyPi`__) and - ``rsa`` (`GitHub`__ | `PyPi`__) modules: - - .. code-block:: sh - - pip3 install pyaes rsa - -2. Clone Telethon's GitHub repository: - - .. code-block:: sh - - git clone https://github.com/LonamiWebs/Telethon.git - -3. Enter the cloned repository: - - .. code-block:: sh - - cd Telethon - -4. Run the code generator: - - .. code-block:: sh - - python3 setup.py gen - -5. Done! - -To generate the `method documentation`__, ``python3 setup.py gen docs``. - - -Optional dependencies -********************* - -If the `cryptg`__ is installed, you might notice a speed-up in the download -and upload speed, since these are the most cryptographic-heavy part of the -library and said module is a C extension. Otherwise, the ``pyaes`` fallback -will be used. - - -__ https://github.com/ricmoo/pyaes -__ https://pypi.python.org/pypi/pyaes -__ https://github.com/sybrenstuvel/python-rsa -__ https://pypi.python.org/pypi/rsa/3.4.2 -__ https://lonamiwebs.github.io/Telethon -__ https://github.com/Lonami/cryptg diff --git a/readthedocs/extra/basic/telegram-client.rst b/readthedocs/extra/basic/telegram-client.rst deleted file mode 100644 index 2e1e7904..00000000 --- a/readthedocs/extra/basic/telegram-client.rst +++ /dev/null @@ -1,108 +0,0 @@ -.. _telegram-client: - -============== -TelegramClient -============== - - -Introduction -************ - -.. note:: - - Make sure to use the friendly methods described in :ref:`telethon-client`! - This section is just an introduction to using the client, but all the - available methods are in the :ref:`telethon-client` reference, including - detailed descriptions to what they do. - -The :ref:`TelegramClient ` is the -central class of the library, the one you will be using most of the time. For -this reason, it's important to know what it offers. - -Since we're working with Python, one must not forget that we can do -``help(client)`` or ``help(TelegramClient)`` at any time for a more -detailed description and a list of all the available methods. Calling -``help()`` from an interactive Python session will always list all the -methods for any object, even yours! - -Interacting with the Telegram API is done through sending **requests**, -this is, any "method" listed on the API. There are a few methods (and -growing!) on the :ref:`TelegramClient ` class that abstract -you from the need of manually importing the requests you need. - -For instance, retrieving your own user can be done in a single line -(assuming you have ``from telethon import sync`` or ``import telethon.sync``): - -.. code-block:: python - - myself = client.get_me() - -Internally, this method has sent a request to Telegram, who replied with -the information about your own user, and then the desired information -was extracted from their response. - -If you want to retrieve any other user, chat or channel (channels are a -special subset of chats), you want to retrieve their "entity". This is -how the library refers to either of these: - -.. code-block:: python - - # The method will infer that you've passed an username - # It also accepts phone numbers, and will get the user - # from your contact list. - lonami = client.get_entity('lonami') - -The so called "entities" are another important whole concept on its own, -but for now you don't need to worry about it. Simply know that they are -a good way to get information about an user, chat or channel. - -Many other common methods for quick scripts are also available: - -.. code-block:: python - - # Note that you can use 'me' or 'self' to message yourself - client.send_message('username', 'Hello World from Telethon!') - - # .send_message's parse mode defaults to markdown, so you - # can use **bold**, __italics__, [links](https://example.com), `code`, - # and even [mentions](@username)/[mentions](tg://user?id=123456789) - client.send_message('username', '**Using** __markdown__ `too`!') - - client.send_file('username', '/home/myself/Pictures/holidays.jpg') - - # The utils package has some goodies, like .get_display_name() - from telethon import utils - for message in client.iter_messages('username', limit=10): - print(utils.get_display_name(message.sender), message.message) - - # Dialogs are the conversations you have open - for dialog in client.get_dialogs(limit=10): - print(dialog.name, dialog.draft.text) - - # Default path is the working directory - client.download_profile_photo('username') - - # Call .disconnect() when you're done - client.disconnect() - -Remember that you can call ``.stringify()`` to any object Telegram returns -to pretty print it. Calling ``str(result)`` does the same operation, but on -a single line. - - -Available methods -***************** - -The :ref:`reference ` lists all the "handy" methods -available for you to use in the :ref:`TelegramClient ` class. -These are simply wrappers around the "raw" Telegram API, making it much more -manageable and easier to work with. - -Please refer to :ref:`accessing-the-full-api` if these aren't enough, -and don't be afraid to read the source code of the InteractiveTelegramClient_ -or even the TelegramClient_ itself to learn how it works. - -See the mentioned :ref:`telethon-client` to find the available methods. - -.. _InteractiveTelegramClient: https://github.com/LonamiWebs/Telethon/blob/master/telethon_examples/interactive_telegram_client.py -.. _TelegramClient: https://github.com/LonamiWebs/Telethon/blob/master/telethon/telegram_client.py diff --git a/readthedocs/extra/basic/working-with-updates.rst b/readthedocs/extra/basic/working-with-updates.rst deleted file mode 100644 index 6aa9341e..00000000 --- a/readthedocs/extra/basic/working-with-updates.rst +++ /dev/null @@ -1,273 +0,0 @@ -.. _working-with-updates: - -==================== -Working with Updates -==================== - -.. important:: - - Make sure you have read at least the first part of :ref:`asyncio-magic` - before working with updates. **This is a big change from Telethon pre-1.0 - and 1.0, and your old handlers won't work with this version**. - - To port your code to the new version, you should just prefix all your - event handlers with ``async`` and ``await`` everything that makes an - API call, such as replying, deleting messages, etc. - - -The library comes with the `telethon.events` module. *Events* are an abstraction -over what Telegram calls `updates`__, and are meant to ease simple and common -usage when dealing with them, since there are many updates. If you're looking -for the method reference, check :ref:`telethon-events-package`, otherwise, -let's dive in! - - -.. important:: - - The library logs by default no output, and any exception that occurs - inside your handlers will be "hidden" from you to prevent the thread - from terminating (so it can still deliver events). You should enable - logging when working with events, at least the error level, to see if - this is happening so you can debug the error. - - **When using updates, please enable logging:** - - .. code-block:: python - - import logging - logging.basicConfig(level=logging.ERROR) - - -.. contents:: - - -Getting Started -*************** - -.. code-block:: python - - from telethon import TelegramClient, events - - client = TelegramClient('name', api_id, api_hash) - - @client.on(events.NewMessage) - async def my_event_handler(event): - if 'hello' in event.raw_text: - await event.reply('hi!') - - client.start() - client.run_until_disconnected() - - -Not much, but there might be some things unclear. What does this code do? - -.. code-block:: python - - from telethon import TelegramClient, events - - client = TelegramClient('name', api_id, api_hash) - - -This is normal creation (of course, pass session name, API ID and hash). -Nothing we don't know already. - -.. code-block:: python - - @client.on(events.NewMessage) - - -This Python decorator will attach itself to the ``my_event_handler`` -definition, and basically means that *on* a `NewMessage -` *event*, -the callback function you're about to define will be called: - -.. code-block:: python - - async def my_event_handler(event): - if 'hello' in event.raw_text: - await event.reply('hi!') - - -If a `NewMessage -` event occurs, -and ``'hello'`` is in the text of the message, we `.reply() -` to the event -with a ``'hi!'`` message. - -Do you notice anything different? Yes! Event handlers **must** be ``async`` -for them to work, and **every method using the network** needs to have an -``await``, otherwise, Python's ``asyncio`` will tell you that you forgot -to do so, so you can easily add it. - -.. code-block:: python - - client.start() - client.run_until_disconnected() - - -Finally, this tells the client that we're done with our code. We run the -``asyncio`` loop until the client starts (this is done behind the scenes, -since the method is so common), and then we run it again until we are -disconnected. Of course, you can do other things instead of running -until disconnected. For this refer to :ref:`update-modes`. - - -More on events -************** - -The `NewMessage ` event has much -more than what was shown. You can access the `.sender -` of the message -through that member, or even see if the message had `.media -`, a `.photo -` or a `.document -` (which you -could download with for example `client.download_media(event.photo) -`. - -If you don't want to `.reply() -` as a reply, -you can use the `.respond() ` -method instead. Of course, there are more events such as `ChatAction -` or `UserUpdate -`, and they're all -used in the same way. Simply add the `@client.on(events.XYZ) -` decorator on the top -of your handler and you're done! The event that will be passed always -is of type ``XYZ.Event`` (for instance, `NewMessage.Event -`), except for the `Raw -` event which just passes the :tl:`Update` object. - -Note that `.reply() -` and `.respond() -` are just wrappers around the -`client.send_message() ` -method which supports the ``file=`` parameter. -This means you can reply with a photo if you do `event.reply(file=photo) -`. - -You can put the same event on many handlers, and even different events on -the same handler. You can also have a handler work on only specific chats, -for example: - - -.. code-block:: python - - import ast - import random - - - # Either a single item or a list of them will work for the chats. - # You can also use the IDs, Peers, or even User/Chat/Channel objects. - @client.on(events.NewMessage(chats=('TelethonChat', 'TelethonOffTopic'))) - async def normal_handler(event): - if 'roll' in event.raw_text: - await event.reply(str(random.randint(1, 6))) - - - # Similarly, you can use incoming=True for messages that you receive - @client.on(events.NewMessage(chats='TelethonOffTopic', outgoing=True, - pattern='eval (.+)')) - async def admin_handler(event): - expression = event.pattern_match.group(1) - await event.reply(str(ast.literal_eval(expression))) - - -You can pass one or more chats to the ``chats`` parameter (as a list or tuple), -and only events from there will be processed. You can also specify whether you -want to handle incoming or outgoing messages (those you receive or those you -send). In this example, people can say ``'roll'`` and you will reply with a -random number, while if you say ``'eval 4+4'``, you will reply with the -solution. Try it! - - -Properties vs. methods -********************** - -The event shown above acts just like a `custom.Message -`, which means you -can access all the properties it has, like ``.sender``. - -**However** events are different to other methods in the client, like -`client.get_messages `. -Events *may not* send information about the sender or chat, which means it -can be ``None``, but all the methods defined in the client always have this -information so it doesn't need to be re-fetched. For this reason, you have -``get_`` methods, which will make a network call if necessary. - -In short, you should do this: - -.. code-block:: python - - @client.on(events.NewMessage) - async def handler(event): - # event.input_chat may be None, use event.get_input_chat() - chat = await event.get_input_chat() - sender = await event.get_sender() - buttons = await event.get_buttons() - - async def main(): - async for message in client.iter_messages('me', 10): - # Methods from the client always have these properties ready - chat = message.input_chat - sender = message.sender - buttons = message.buttons - -Notice, properties (`message.sender -`) don't need an ``await``, but -methods (`message.get_sender -`) **do** need an ``await``, -and you should use methods in events for these properties that may need network. - - -Events without decorators -************************* - -If for any reason you can't use the `@client.on -` syntax, don't worry. -You can call `client.add_event_handler(callback, event) -` to achieve -the same effect. - -Similarly, you also have `client.remove_event_handler -` -and `client.list_event_handlers -`. - -The ``event`` type is optional in all methods and defaults to -`events.Raw ` for adding, and ``None`` when -removing (so all callbacks would be removed). - - -Stopping propagation of Updates -******************************* - -There might be cases when an event handler is supposed to be used solitary and -it makes no sense to process any other handlers in the chain. For this case, -it is possible to raise a `telethon.events.StopPropagation` exception which -will cause the propagation of the update through your handlers to stop: - -.. code-block:: python - - from telethon.events import StopPropagation - - @client.on(events.NewMessage) - async def _(event): - # ... some conditions - await event.delete() - - # Other handlers won't have an event to work with - raise StopPropagation - - @client.on(events.NewMessage) - async def _(event): - # Will never be reached, because it is the second handler - # in the chain. - pass - - -Remember to check :ref:`telethon-events-package` if you're looking for -the methods reference. - - -__ https://lonamiwebs.github.io/Telethon/types/update.html diff --git a/readthedocs/extra/changelog.rst b/readthedocs/extra/changelog.rst deleted file mode 100644 index 228fc8ab..00000000 --- a/readthedocs/extra/changelog.rst +++ /dev/null @@ -1,2235 +0,0 @@ -.. _changelog: - - -=========================== -Changelog (Version History) -=========================== - - -This page lists all the available versions of the library, -in chronological order. You should read this when upgrading -the library to know where your code can break, and where -it can take advantage of new goodies! - -.. contents:: List of All Versions - - -Bug Fixes (v1.0.1) -================== - -*Published at 2018/06/27* - -And as usual, every major release has a few bugs that make the library -unusable! This quick update should fix those, namely: - -Bug fixes -~~~~~~~~~ - -- `client.start() ` was completely - broken due to a last-time change requiring named arguments everywhere. -- Since the rewrite, if your system clock was wrong, the connection would - get stuck in an infinite "bad message" loop of responses from Telegram. -- Accessing the buttons of a custom message wouldn't work in channels, - which lead to fix a completely different bug regarding starting bots. -- Disconnecting could complain if the magic ``telethon.sync`` was imported. -- Successful automatic reconnections now ask Telegram to send updates to us - once again as soon as the library is ready to listen for them. - - -Synchronous magic (v1.0) -======================== - -*Published at 2018/06/27* - -.. important:: - - If you come from Telethon pre-1.0 you **really** want to read - :ref:`asyncio-magic` to port your scripts to the new version. - - If you're not ready for this, you can ``pip install telethon==0.19.1.6``. - It's the latest version of the library using threads for Python 3.4+. - - If you're interested in maintaining a Telethon version that supports - Python 3.4 and uses threads, please open an issue and let me know. - -The library has been around for well over a year. A lot of improvements have -been made, a lot of user complaints have been fixed, and a lot of user desires -have been implemented. It's time to consider the public API as stable, and -remove some of the old methods that were around until now for compatibility -reasons. But there's one more surprise! - -There is a new magic ``telethon.sync`` module to let you use **all** the -methods in the :ref:`TelegramClient ` (and the types returned -from its functions) in a synchronous way, while using ``asyncio`` behind -the scenes! This means you're now able to do both of the following: - -.. code-block:: python - - import asyncio - - async def main(): - await client.send_message('me', 'Hello!') - - asyncio.get_event_loop().run_until_complete(main()) - - # ...can be rewritten as: - - from telethon import sync - client.send_message('me', 'Hello!') - -Both ways can coexist (you need to ``await`` if the loop is running). - -You can also use the magic ``sync`` module in your own classes, and call -``sync.syncify(cls)`` to convert all their ``async def`` into magic variants. - - - -Breaking Changes -~~~~~~~~~~~~~~~~ - -- ``message.get_fwd_sender`` is now in `message.forward - `. -- ``client.add_update_handler`` is now `client.add_event_handler - ` -- ``client.remove_update_handler`` is now `client.remove_event_handler - ` -- ``client.list_update_handlers`` is now `client.list_event_handlers - ` -- ``client.get_message_history`` is now `client.get_messages - ` -- ``client.send_voice_note`` is now `client.send_file - ` with ``is_voice=True``. -- ``client.invoke()`` is now ``client(...)``. -- ``report_errors`` has been removed since it's currently not used, - and ``flood_sleep_threshold`` is now part of the client. -- Methods with a lot of arguments can no longer be used without specifying - their argument. Instead you need to use named arguments. This improves - readability and not needing to learn the order of the arguments, which - can also change. - - -Additions -~~~~~~~~~ - -- `client.send_file ` now - accepts external ``http://`` and ``https://`` URLs. -- You can use the :ref:`TelegramClient ` inside of ``with`` - blocks, which will `client.start() ` - and `disconnect() ` - the client for you: - - .. code-block:: python - - from telethon import TelegramClient, sync - - with TelegramClient(name, api_id, api_hash) as client: - client.send_message('me', 'Hello!') - - Convenience at its maximum! You can even chain the `.start() - ` method since - it returns the instance of the client: - - .. code-block:: python - - with TelegramClient(name, api_id, api_hash).start(bot_token=token) as bot: - bot.send_message(chat, 'Hello!') - - -Bug fixes -~~~~~~~~~ - -- There were some ``@property async def`` left, and some ``await property``. -- "User joined" event was being treated as "User was invited". -- SQLite's cursor should not be closed properly after usage. -- ``await`` the updates task upon disconnection. -- Some bug in Python 3.5.2's ``asyncio`` causing 100% CPU load if you - forgot to call `client.disconnect() - `. - The method is called for you on object destruction, but you still should - disconnect manually or use a ``with`` block. -- Some fixes regarding disconnecting on client deletion and properly - saving the authorization key. -- Passing a class to `message.get_entities_text - ` now works properly. -- Iterating messages from a specific user in private messages now works. - -Enhancements -~~~~~~~~~~~~ - -- Both `client.start() ` and - `client.run_until_disconnected() - ` can - be ran in both a synchronous way (without starting the loop manually) - or from an ``async def`` where they need to have an ``await``. - - -Core Rewrite in asyncio (v1.0-rc1) -================================== - -*Published at 2018/06/24* - -+-----------------------+ -| Scheme layer used: 81 | -+-----------------------+ - -This version is a major overhaul of the library internals. The core has -been rewritten, cleaned up and refactored to fix some oddities that have -been growing inside the library. - -This means that the code is easier to understand and reason about, -including the code flow such as conditions, exceptions, where to -reconnect, how the library should behave, and separating different -retry types such as disconnections or call fails, but it also means -that **some things will necessarily break** in this version. - -All requests that touch the network are now methods and need to -have their ``await`` (or be ran until their completion). - -Also, the library finally has the simple logo it deserved: a carefully -hand-written ``.svg`` file representing a T following Python's colours. - - -Breaking Changes -~~~~~~~~~~~~~~~~ - -- If you relied on internals like the ``MtProtoSender`` and the - ``TelegramBareClient``, both are gone. They are now `MTProtoSender - ` and `TelegramBaseClient - ` and they behave - differently. -- Underscores have been renamed from filenames. This means - ``telethon.errors.rpc_error_list`` won't work, but you should - have been using `telethon.errors` all this time instead. -- `client.connect ` - no longer returns ``True`` on success. Instead, you should ``except`` the - possible ``ConnectionError`` and act accordingly. This makes it easier to - not ignore the error. -- You can no longer set ``retries=n`` when calling a request manually. The - limit works differently now, and it's done on a per-client basis. -- Accessing `.sender `, - `.chat ` and similar may *not* work - in events anymore, since previously they could access the network. The new - rule is that properties are not allowed to make API calls. You should use - `.get_sender() `, - `.get_chat() ` instead while - using events. You can safely access properties if you get messages through - `client.get_messages() ` - or other methods in the client. -- The above point means ``reply_message`` is now `.get_reply_message() - `, and ``fwd_from_entity`` - is now `get_fwd_sender() `. - Also ``forward`` was gone in the previous version, and you should be using - ``fwd_from`` instead. - - -Additions -~~~~~~~~~ - -- Telegram's Terms Of Service are now accepted when creating a new account. - This can possibly help avoid bans. This has no effect for accounts that - were created before. -- The `method reference `_ now shows - which methods can be used if you sign in with a ``bot_token``. -- There's a new `client.disconnected - ` future - which you can wait on. When a disconnection occurs, you will now, instead - letting it happen in the background. -- More configurable retries parameters, such as auto-reconnection, retries - when connecting, and retries when sending a request. -- You can filter `events.NewMessage ` - by sender ID, and also whether they are forwards or not. -- New ``ignore_migrated`` parameter for `client.iter_dialogs - `. - -Bug fixes -~~~~~~~~~ - -- Several fixes to `telethon.events.newmessage.NewMessage`. -- Removed named ``length`` argument in ``to_bytes`` for PyPy. -- Raw events failed due to not having ``._set_client``. -- `message.get_entities_text - ` properly - supports filtering, even if there are no message entities. -- `message.click ` works better. -- The server started sending :tl:`DraftMessageEmpty` which the library - didn't handle correctly when getting dialogs. -- The "correct" chat is now always returned from returned messages. -- ``to_id`` was not validated when retrieving messages by their IDs. -- ``'__'`` is no longer considered valid in usernames. -- The ``fd`` is removed from the reader upon closing the socket. This - should be noticeable in Windows. -- :tl:`MessageEmpty` is now handled when searching messages. -- Fixed a rare infinite loop bug in `client.iter_dialogs - ` for some people. -- Fixed ``TypeError`` when there is no `.sender - `. - -Enhancements -~~~~~~~~~~~~ - -- You can now delete over 100 messages at once with `client.delete_messages - `. -- Signing in now accounts for ``AuthRestartError`` itself, and also handles - ``PasswordHashInvalidError``. -- ``__all__`` is now defined, so ``from telethon import *`` imports sane - defaults (client, events and utils). This is however discouraged and should - be used only in quick scripts. -- ``pathlib.Path`` is now supported for downloading and uploading media. -- Messages you send to yourself are now considered outgoing, unless they - are forwarded. -- The documentation has been updated with a brand new ``asyncio`` crash - course to encourage you use it. You can still use the threaded version - if you want though. -- ``.name`` property is now properly supported when sending and downloading - files. -- Custom ``parse_mode``, which can now be set per-client, support - :tl:`MessageEntityMentionName` so you can return those now. -- The session file is saved less often, which could result in a noticeable - speed-up when working with a lot of incoming updates. - - -Internal changes -~~~~~~~~~~~~~~~~ - -- The flow for sending a request is as follows: the ``TelegramClient`` creates - a ``MTProtoSender`` with a ``Connection``, and the sender starts send and - receive loops. Sending a request means enqueueing it in the sender, which - will eventually pack and encrypt it with its ``ConnectionState`` instead - of using the entire ``Session`` instance. When the data is packed, it will - be sent over the ``Connection`` and ultimately over the ``TcpClient``. - -- Reconnection occurs at the ``MTProtoSender`` level, and receiving responses - follows a similar process, but now ``asyncio.Future`` is used for the results - which are no longer part of all ``TLObject``, instead are part of the - ``TLMessage`` which simplifies things. - -- Objects can no longer be ``content_related`` and instead subclass - ``TLRequest``, making the separation of concerns easier. - -- The ``TelegramClient`` has been split into several mixin classes to avoid - having a 3,000-lines-long file with all the methods. - -- More special cases in the ``MTProtoSender`` have been cleaned up, and also - some attributes from the ``Session`` which didn't really belong there since - they weren't being saved. - -- The ``telethon_generator/`` can now convert ``.tl`` files into ``.json``, - mostly as a proof of concept, but it might be useful for other people. - - -Custom Message class (v0.19.1) -============================== - -*Published at 2018/06/03* - -+-----------------------+ -| Scheme layer used: 80 | -+-----------------------+ - - -This update brings a new `telethon.tl.custom.message.Message` object! - -All the methods in the `telethon.telegram_client.TelegramClient` that -used to return a :tl:`Message` will now return this object instead, which -means you can do things like the following: - -.. code-block:: python - - msg = client.send_message(chat, 'Hello!') - msg.edit('Hello there!') - msg.reply('Good day!') - print(msg.sender) - -Refer to its documentation to see all you can do, again, click -`telethon.tl.custom.message.Message` to go to its page. - - -Breaking Changes -~~~~~~~~~~~~~~~~ - -- The `telethon.network.connection.common.Connection` class is now an ABC, - and the old ``ConnectionMode`` is now gone. Use a specific connection (like - `telethon.network.connection.tcpabridged.ConnectionTcpAbridged`) instead. - -Additions -~~~~~~~~~ - -- You can get messages by their ID with - `telethon.telegram_client.TelegramClient.get_messages`'s ``ids`` parameter: - - .. code-block:: python - - message = client.get_messages(chats, ids=123) # Single message - message_list = client.get_messages(chats, ids=[777, 778]) # Multiple - -- More convenience properties for `telethon.tl.custom.dialog.Dialog`. -- New default `telethon.telegram_client.TelegramClient.parse_mode`. -- You can edit the media of messages that already have some media. -- New dark theme in the online ``tl`` reference, check it out at - https://lonamiwebs.github.io/Telethon/. - -Bug fixes -~~~~~~~~~ - -- Some IDs start with ``1000`` and these would be wrongly treated as channels. -- Some short usernames like ``@vote`` were being ignored. -- `telethon.telegram_client.TelegramClient.iter_messages`'s ``from_user`` - was failing if no filter had been set. -- `telethon.telegram_client.TelegramClient.iter_messages`'s ``min_id/max_id`` - was being ignored by Telegram. This is now worked around. -- `telethon.telegram_client.TelegramClient.catch_up` would fail with empty - states. -- `telethon.events.newmessage.NewMessage` supports ``incoming=False`` - to indicate ``outgoing=True``. - -Enhancements -~~~~~~~~~~~~ - -- You can now send multiple requests at once while preserving the order: - - .. code-block:: python - - from telethon.tl.functions.messages import SendMessageRequest - client([SendMessageRequest(chat, 'Hello 1!'), - SendMessageRequest(chat, 'Hello 2!')], ordered=True) - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``without rowid`` is not used in SQLite anymore. -- Unboxed serialization would fail. -- Different default limit for ``iter_messages`` and ``get_messages``. -- Some clean-up in the ``telethon_generator/`` package. - - -Catching up on Updates (v0.19) -============================== - -*Published at 2018/05/07* - -+-----------------------+ -| Scheme layer used: 76 | -+-----------------------+ - -This update prepares the library for catching up with updates with the new -`telethon.telegram_client.TelegramClient.catch_up` method. This feature needs -more testing, but for now it will let you "catch up" on some old updates that -occurred while the library was offline, and brings some new features and bug -fixes. - - -Additions -~~~~~~~~~ - -- Add ``search``, ``filter`` and ``from_user`` parameters to - `telethon.telegram_client.TelegramClient.iter_messages`. -- `telethon.telegram_client.TelegramClient.download_file` now - supports a ``None`` path to return the file in memory and - return its ``bytes``. -- Events now have a ``.original_update`` field. - -Bug fixes -~~~~~~~~~ - -- Fixed a race condition when receiving items from the network. -- A disconnection is made when "retries reached 0". This hasn't been - tested but it might fix the bug. -- ``reply_to`` would not override :tl:`Message` object's reply value. -- Add missing caption when sending :tl:`Message` with media. - -Enhancements -~~~~~~~~~~~~ - -- Retry automatically on ``RpcCallFailError``. This error happened a lot - when iterating over many messages, and retrying often fixes it. -- Faster `telethon.telegram_client.TelegramClient.iter_messages` by - sleeping only as much as needed. -- `telethon.telegram_client.TelegramClient.edit_message` now supports - omitting the entity if you pass a :tl:`Message`. -- `telethon.events.raw.Raw` can now be filtered by type. - -Internal changes -~~~~~~~~~~~~~~~~ - -- The library now distinguishes between MTProto and API schemas. -- :tl:`State` is now persisted to the session file. -- Connection won't retry forever. -- Fixed some errors and cleaned up the generation of code. -- Fixed typos and enhanced some documentation in general. -- Add auto-cast for :tl:`InputMessage` and :tl:`InputLocation`. - - -Pickle-able objects (v0.18.3) -============================= - -*Published at 2018/04/15* - - -Now you can use Python's ``pickle`` module to serialize ``RPCError`` and -any other ``TLObject`` thanks to **@vegeta1k95**! A fix that was fairly -simple, but still might be useful for many people. - -As a side note, the documentation at https://lonamiwebs.github.io/Telethon -now lists known ``RPCError`` for all requests, so you know what to expect. -This required a major rewrite, but it was well worth it! - -Breaking changes -~~~~~~~~~~~~~~~~ - -- `telethon.telegram_client.TelegramClient.forward_messages` now returns - a single item instead of a list if the input was also a single item. - -Additions -~~~~~~~~~ - -- New `telethon.events.messageread.MessageRead` event, to find out when - and who read which messages as soon as it happens. -- Now you can access ``.chat_id`` on all events and ``.sender_id`` on some. - -Bug fixes -~~~~~~~~~ - -- Possibly fix some bug regarding lost ``GzipPacked`` requests. -- The library now uses the "real" layer 75, hopefully. -- Fixed ``.entities`` name collision on updates by making it private. -- ``AUTH_KEY_DUPLICATED`` is handled automatically on connection. -- Markdown parser's offset uses ``match.start()`` to allow custom regex. -- Some filter types (as a type) were not supported by - `telethon.telegram_client.TelegramClient.iter_participants`. -- `telethon.telegram_client.TelegramClient.remove_event_handler` works. -- `telethon.telegram_client.TelegramClient.start` works on all terminals. -- :tl:`InputPeerSelf` case was missing from - `telethon.telegram_client.TelegramClient.get_input_entity`. - -Enhancements -~~~~~~~~~~~~ - -- The ``parse_mode`` for messages now accepts a callable. -- `telethon.telegram_client.TelegramClient.download_media` accepts web previews. -- `telethon.tl.custom.dialog.Dialog` instances can now be casted into - :tl:`InputPeer`. -- Better logging when reading packages "breaks". -- Better and more powerful ``setup.py gen`` command. - -Internal changes -~~~~~~~~~~~~~~~~ - -- The library won't call ``.get_dialogs()`` on entity not found. Instead, - it will ``raise ValueError()`` so you can properly ``except`` it. -- Several new examples and updated documentation. -- ``py:obj`` is the default Sphinx's role which simplifies ``.rst`` files. -- ``setup.py`` now makes use of ``python_requires``. -- Events now live in separate files. -- Other minor changes. - - -Several bug fixes (v0.18.2) -=========================== - -*Published at 2018/03/27* - -Just a few bug fixes before they become too many. - -Additions -~~~~~~~~~ - -- Getting an entity by its positive ID should be enough, regardless of their - type (whether it's an ``User``, a ``Chat`` or a ``Channel``). Although - wrapping them inside a ``Peer`` is still recommended, it's not necessary. -- New ``client.edit_2fa`` function to change your Two Factor Authentication - settings. -- ``.stringify()`` and string representation for custom ``Dialog/Draft``. - -Bug fixes -~~~~~~~~~ - -- Some bug regarding ``.get_input_peer``. -- ``events.ChatAction`` wasn't picking up all the pins. -- ``force_document=True`` was being ignored for albums. -- Now you're able to send ``Photo`` and ``Document`` as files. -- Wrong access to a member on chat forbidden error for ``.get_participants``. - An empty list is returned instead. -- ``me/self`` check for ``.get[_input]_entity`` has been moved up so if - someone has "me" or "self" as their name they won't be retrieved. - - -Iterator methods (v0.18.1) -========================== - -*Published at 2018/03/17* - -All the ``.get_`` methods in the ``TelegramClient`` now have a ``.iter_`` -counterpart, so you can do operations while retrieving items from them. -For instance, you can ``client.iter_dialogs()`` and ``break`` once you -find what you're looking for instead fetching them all at once. - -Another big thing, you can get entities by just their positive ID. This -may cause some collisions (although it's very unlikely), and you can (should) -still be explicit about the type you want. However, it's a lot more convenient -and less confusing. - -Breaking changes -~~~~~~~~~~~~~~~~ - -- The library only offers the default ``SQLiteSession`` again. - See :ref:`sessions` for more on how to use a different storage from now on. - -Additions -~~~~~~~~~ - -- Events now override ``__str__`` and implement ``.stringify()``, just like - every other ``TLObject`` does. -- ``events.ChatAction`` now has :meth:`respond`, :meth:`reply` and - :meth:`delete` for the message that triggered it. -- :meth:`client.iter_participants` (and its :meth:`client.get_participants` - counterpart) now expose the ``filter`` argument, and the returned users - also expose the ``.participant`` they are. -- You can now use :meth:`client.remove_event_handler` and - :meth:`client.list_event_handlers` similar how you could with normal updates. -- New properties on ``events.NewMessage``, like ``.video_note`` and ``.gif`` - to access only specific types of documents. -- The ``Draft`` class now exposes ``.text`` and ``.raw_text``, as well as a - new :meth:`Draft.send` to send it. - -Bug fixes -~~~~~~~~~ - -- ``MessageEdited`` was ignoring ``NewMessage`` constructor arguments. -- Fixes for ``Event.delete_messages`` which wouldn't handle ``MessageService``. -- Bot API style IDs not working on :meth:`client.get_input_entity`. -- :meth:`client.download_media` didn't support ``PhotoSize``. - -Enhancements -~~~~~~~~~~~~ - -- Less RPC are made when accessing the ``.sender`` and ``.chat`` of some - events (mostly those that occur in a channel). -- You can send albums larger than 10 items (they will be sliced for you), - as well as mixing normal files with photos. -- ``TLObject`` now have Python type hints. - -Internal changes -~~~~~~~~~~~~~~~~ - -- Several documentation corrections. -- :meth:`client.get_dialogs` is only called once again when an entity is - not found to avoid flood waits. - - -Sessions overhaul (v0.18) -========================= - -*Published at 2018/03/04* - -+-----------------------+ -| Scheme layer used: 75 | -+-----------------------+ - -The ``Session``'s have been revisited thanks to the work of **@tulir** and -they now use an `ABC `__ so you -can easily implement your own! - -The default will still be a ``SQLiteSession``, but you might want to use -the new ``AlchemySessionContainer`` if you need. Refer to the section of -the documentation on :ref:`sessions` for more. - -Breaking changes -~~~~~~~~~~~~~~~~ - -- ``events.MessageChanged`` doesn't exist anymore. Use the new - ``events.MessageEdited`` and ``events.MessageDeleted`` instead. - -Additions -~~~~~~~~~ - -- The mentioned addition of new session types. -- You can omit the event type on ``client.add_event_handler`` to use ``Raw``. -- You can ``raise StopPropagation`` of events if you added several of them. -- ``.get_participants()`` can now get up to 90,000 members from groups with - 100,000 if when ``aggressive=True``, "bypassing" Telegram's limit. -- You now can access ``NewMessage.Event.pattern_match``. -- Multiple captions are now supported when sending albums. -- ``client.send_message()`` has an optional ``file=`` parameter, so - you can do ``events.reply(file='/path/to/photo.jpg')`` and similar. -- Added ``.input_`` versions to ``events.ChatAction``. -- You can now access the public ``.client`` property on ``events``. -- New ``client.forward_messages``, with its own wrapper on ``events``, - called ``event.forward_to(...)``. - - -Bug fixes -~~~~~~~~~ - -- Silly bug regarding ``client.get_me(input_peer=True)``. -- ``client.send_voice_note()`` was missing some parameters. -- ``client.send_file()`` plays better with streams now. -- Incoming messages from bots weren't working with whitelists. -- Markdown's URL regex was not accepting newlines. -- Better attempt at joining background update threads. -- Use the right peer type when a marked integer ID is provided. - - -Internal changes -~~~~~~~~~~~~~~~~ - -- Resolving ``events.Raw`` is now a no-op. -- Logging calls in the ``TcpClient`` to spot errors. -- ``events`` resolution is postponed until you are successfully connected, - so you can attach them before starting the client. -- When an entity is not found, it is searched in *all* dialogs. This might - not always be desirable but it's more comfortable for legitimate uses. -- Some non-persisting properties from the ``Session`` have been moved out. - - -Further easing library usage (v0.17.4) -====================================== - -*Published at 2018/02/24* - -Some new things and patches that already deserved their own release. - - -Additions -~~~~~~~~~ - -- New ``pattern`` argument to ``NewMessage`` to easily filter messages. -- New ``.get_participants()`` convenience method to get members from chats. -- ``.send_message()`` now accepts a ``Message`` as the ``message`` parameter. -- You can now ``.get_entity()`` through exact name match instead username. -- Raise ``ProxyConnectionError`` instead looping forever so you can - ``except`` it on your own code and behave accordingly. - -Bug fixes -~~~~~~~~~ - -- ``.parse_username`` would fail with ``www.`` or a trailing slash. -- ``events.MessageChanged`` would fail with ``UpdateDeleteMessages``. -- You can now send ``b'byte strings'`` directly as files again. -- ``.send_file()`` was not respecting the original captions when passing - another message (or media) as the file. -- Downloading media from a different data center would always log a warning - for the first time. - -Internal changes -~~~~~~~~~~~~~~~~ - -- Use ``req_pq_multi`` instead ``req_pq`` when generating ``auth_key``. -- You can use ``.get_me(input_peer=True)`` if all you need is your self ID. -- New addition to the interactive client example to show peer information. -- Avoid special casing ``InputPeerSelf`` on some ``NewMessage`` events, so - you can always safely rely on ``.sender`` to get the right ID. - - -New small convenience functions (v0.17.3) -========================================= - -*Published at 2018/02/18* - -More bug fixes and a few others addition to make events easier to use. - -Additions -~~~~~~~~~ - -- Use ``hachoir`` to extract video and audio metadata before upload. -- New ``.add_event_handler``, ``.add_update_handler`` now deprecated. - -Bug fixes -~~~~~~~~~ - -- ``bot_token`` wouldn't work on ``.start()``, and changes to ``password`` - (now it will ask you for it if you don't provide it, as docstring hinted). -- ``.edit_message()`` was ignoring the formatting (e.g. markdown). -- Added missing case to the ``NewMessage`` event for normal groups. -- Accessing the ``.text`` of the ``NewMessage`` event was failing due - to a bug with the markdown unparser. - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``libssl`` is no longer an optional dependency. Use ``cryptg`` instead, - which you can find on https://github.com/Lonami/cryptg. - - - -New small convenience functions (v0.17.2) -========================================= - -*Published at 2018/02/15* - -Primarily bug fixing and a few welcomed additions. - -Additions -~~~~~~~~~ - -- New convenience ``.edit_message()`` method on the ``TelegramClient``. -- New ``.edit()`` and ``.delete()`` shorthands on the ``NewMessage`` event. -- Default to markdown parsing when sending and editing messages. -- Support for inline mentions when sending and editing messages. They work - like inline urls (e.g. ``[text](@username)``) and also support the Bot-API - style (see `here `__). - -Bug fixes -~~~~~~~~~ - -- Periodically send ``GetStateRequest`` automatically to keep the server - sending updates even if you're not invoking any request yourself. -- HTML parsing was failing due to not handling surrogates properly. -- ``.sign_up`` was not accepting ``int`` codes. -- Whitelisting more than one chat on ``events`` wasn't working. -- Video files are sent as a video by default unless ``force_document``. - -Internal changes -~~~~~~~~~~~~~~~~ - -- More ``logging`` calls to help spot some bugs in the future. -- Some more logic to retrieve input entities on events. -- Clarified a few parts of the documentation. - - -Updates as Events (v0.17.1) -=========================== - -*Published at 2018/02/09* - -Of course there was more work to be done regarding updates, and it's here! -The library comes with a new ``events`` module (which you will often import -as ``from telethon import TelegramClient, events``). This are pretty much -all the additions that come with this version change, but they are a nice -addition. Refer to :ref:`working-with-updates` to get started with events. - - -Trust the Server with Updates (v0.17) -===================================== - -*Published at 2018/02/03* - -The library trusts the server with updates again. The library will *not* -check for duplicates anymore, and when the server kicks us, it will run -``GetStateRequest`` so the server starts sending updates again (something -it wouldn't do unless you invoked something, it seems). But this update -also brings a few more changes! - -Additions -~~~~~~~~~ - -- ``TLObject``'s override ``__eq__`` and ``__ne__``, so you can compare them. -- Added some missing cases on ``.get_input_entity()`` and peer functions. -- ``obj.to_dict()`` now has a ``'_'`` key with the type used. -- ``.start()`` can also sign up now. -- More parameters for ``.get_message_history()``. -- Updated list of RPC errors. -- HTML parsing thanks to **@tulir**! It can be used similar to markdown: - ``client.send_message(..., parse_mode='html')``. - - -Enhancements -~~~~~~~~~~~~ - -- ``client.send_file()`` now accepts ``Message``'s and - ``MessageMedia``'s as the ``file`` parameter. -- Some documentation updates and fixed to clarify certain things. -- New exact match feature on https://lonamiwebs.github.io/Telethon. -- Return as early as possible from ``.get_input_entity()`` and similar, - to avoid penalizing you for doing this right. - -Bug fixes -~~~~~~~~~ - -- ``.download_media()`` wouldn't accept a ``Document`` as parameter. -- The SQLite is now closed properly on disconnection. -- IPv6 addresses shouldn't use square braces. -- Fix regarding ``.log_out()``. -- The time offset wasn't being used (so having wrong system time would - cause the library not to work at all). - - -New ``.resolve()`` method (v0.16.2) -=================================== - -*Published at 2018/01/19* - -The ``TLObject``'s (instances returned by the API and ``Request``'s) have -now acquired a new ``.resolve()`` method. While this should be used by the -library alone (when invoking a request), it means that you can now use -``Peer`` types or even usernames where a ``InputPeer`` is required. The -object now has access to the ``client``, so that it can fetch the right -type if needed, or access the session database. Furthermore, you can -reuse requests that need "autocast" (e.g. you put :tl:`User` but ``InputPeer`` -was needed), since ``.resolve()`` is called when invoking. Before, it was -only done on object construction. - -Additions -~~~~~~~~~ - -- Album support. Just pass a list, tuple or any iterable to ``.send_file()``. - - -Enhancements -~~~~~~~~~~~~ - -- ``.start()`` asks for your phone only if required. -- Better file cache. All files under 10MB, once uploaded, should never be - needed to be re-uploaded again, as the sent media is cached to the session. - - -Bug fixes -~~~~~~~~~ - -- ``setup.py`` now calls ``gen_tl`` when installing the library if needed. - - -Internal changes -~~~~~~~~~~~~~~~~ - -- The mentioned ``.resolve()`` to perform "autocast", more powerful. -- Upload and download methods are no longer part of ``TelegramBareClient``. -- Reuse ``.on_response()``, ``.__str__`` and ``.stringify()``. - Only override ``.on_response()`` if necessary (small amount of cases). -- Reduced "autocast" overhead as much as possible. - You shouldn't be penalized if you've provided the right type. - - -MtProto 2.0 (v0.16.1) -===================== - -*Published at 2018/01/11* - -+-----------------------+ -| Scheme layer used: 74 | -+-----------------------+ - -The library is now using MtProto 2.0! This shouldn't really affect you -as an end user, but at least it means the library will be ready by the -time MtProto 1.0 is deprecated. - -Additions -~~~~~~~~~ - -- New ``.start()`` method, to make the library avoid boilerplate code. -- ``.send_file`` accepts a new optional ``thumbnail`` parameter, and - returns the ``Message`` with the sent file. - - -Bug fixes -~~~~~~~~~ - -- The library uses again only a single connection. Less updates are - be dropped now, and the performance is even better than using temporary - connections. -- ``without rowid`` will only be used on the ``*.session`` if supported. -- Phone code hash is associated with phone, so you can change your mind - when calling ``.sign_in()``. - - -Internal changes -~~~~~~~~~~~~~~~~ - -- File cache now relies on the hash of the file uploaded instead its path, - and is now persistent in the ``*.session`` file. Report any bugs on this! -- Clearer error when invoking without being connected. -- Markdown parser doesn't work on bytes anymore (which makes it cleaner). - - -Sessions as sqlite databases (v0.16) -==================================== - -*Published at 2017/12/28* - -In the beginning, session files used to be pickle. This proved to be bad -as soon as one wanted to add more fields. For this reason, they were -migrated to use JSON instead. But this proved to be bad as soon as one -wanted to save things like entities (usernames, their ID and hash), so -now it properly uses -`sqlite3 `__, -which has been well tested, to save the session files! Calling -``.get_input_entity`` using a ``username`` no longer will need to fetch -it first, so it's really 0 calls again. Calling ``.get_entity`` will -always fetch the most up to date version. - -Furthermore, nearly everything has been documented, thus preparing the -library for `Read the Docs `__ (although there -are a few things missing I'd like to polish first), and the -`logging `__ are now -better placed. - -Breaking changes -~~~~~~~~~~~~~~~~ - -- ``.get_dialogs()`` now returns a **single list** instead a tuple - consisting of a **custom class** that should make everything easier - to work with. -- ``.get_message_history()`` also returns a **single list** instead a - tuple, with the ``Message`` instances modified to make them more - convenient. - -Both lists have a ``.total`` attribute so you can still know how many -dialogs/messages are in total. - -Additions -~~~~~~~~~ - -- The mentioned use of ``sqlite3`` for the session file. -- ``.get_entity()`` now supports lists too, and it will make as little - API calls as possible if you feed it ``InputPeer`` types. Usernames - will always be resolved, since they may have changed. -- ``.set_proxy()`` method, to avoid having to create a new - ``TelegramClient``. -- More ``date`` types supported to represent a date parameter. - -Bug fixes -~~~~~~~~~ - -- Empty strings weren't working when they were a flag parameter (e.g., - setting no last name). -- Fix invalid assertion regarding flag parameters as well. -- Avoid joining the background thread on disconnect, as it would be - ``None`` due to a race condition. -- Correctly handle ``None`` dates when downloading media. -- ``.download_profile_photo`` was failing for some channels. -- ``.download_media`` wasn't handling ``Photo``. - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``date`` was being serialized as local date, but that was wrong. -- ``date`` was being represented as a ``float`` instead of an ``int``. -- ``.tl`` parser wasn't stripping inline comments. -- Removed some redundant checks on ``update_state.py``. -- Use a `synchronized - queue `__ instead a - hand crafted version. -- Use signed integers consistently (e.g. ``salt``). -- Always read the corresponding ``TLObject`` from API responses, except - for some special cases still. -- A few more ``except`` low level to correctly wrap errors. -- More accurate exception types. -- ``invokeWithLayer(initConnection(X))`` now wraps every first request - after ``.connect()``. - -As always, report if you have issues with some of the changes! - -IPv6 support (v0.15.5) -====================== - -*Published at 2017/11/16* - -+-----------------------+ -| Scheme layer used: 73 | -+-----------------------+ - -It's here, it has come! The library now **supports IPv6**! Just pass -``use_ipv6=True`` when creating a ``TelegramClient``. Note that I could -*not* test this feature because my machine doesn't have IPv6 setup. If -you know IPv6 works in your machine but the library doesn't, please -refer to `#425 `_. - -Additions -~~~~~~~~~ - -- IPv6 support. -- New method to extract the text surrounded by ``MessageEntity``\ 's, - in the ``extensions.markdown`` module. - -Enhancements -~~~~~~~~~~~~ - -- Markdown parsing is Done Right. -- Reconnection on failed invoke. Should avoid "number of retries - reached 0" (#270). -- Some missing autocast to ``Input*`` types. -- The library uses the ``NullHandler`` for ``logging`` as it should - have always done. -- ``TcpClient.is_connected()`` is now more reliable. - -.. bug-fixes-1: - -Bug fixes -~~~~~~~~~ - -- Getting an entity using their phone wasn't actually working. -- Full entities aren't saved unless they have an ``access_hash``, to - avoid some ``None`` errors. -- ``.get_message_history`` was failing when retrieving items that had - messages forwarded from a channel. - -General enhancements (v0.15.4) -============================== - -*Published at 2017/11/04* - -+-----------------------+ -| Scheme layer used: 72 | -+-----------------------+ - -This update brings a few general enhancements that are enough to deserve -a new release, with a new feature: beta **markdown-like parsing** for -``.send_message()``! - -.. additions-1: - -Additions -~~~~~~~~~ - -- ``.send_message()`` supports ``parse_mode='md'`` for **Markdown**! It - works in a similar fashion to the official clients (defaults to - double underscore/asterisk, like ``**this**``). Please report any - issues with emojies or enhancements for the parser! -- New ``.idle()`` method so your main thread can do useful job (listen - for updates). -- Add missing ``.to_dict()``, ``__str__`` and ``.stringify()`` for - ``TLMessage`` and ``MessageContainer``. - -.. bug-fixes-2: - -Bug fixes -~~~~~~~~~ - -- The list of known peers could end "corrupted" and have users with - ``access_hash=None``, resulting in ``struct`` error for it not being - an integer. You shouldn't encounter this issue anymore. -- The warning for "added update handler but no workers set" wasn't - actually working. -- ``.get_input_peer`` was ignoring a case for ``InputPeerSelf``. -- There used to be an exception when logging exceptions (whoops) on - update handlers. -- "Downloading contacts" would produce strange output if they had - semicolons (``;``) in their name. -- Fix some cyclic imports and installing dependencies from the ``git`` - repository. -- Code generation was using f-strings, which are only supported on - Python ≥3.6. - -Internal changes -~~~~~~~~~~~~~~~~ - -- The ``auth_key`` generation has been moved from ``.connect()`` to - ``.invoke()``. There were some issues were ``.connect()`` failed and - the ``auth_key`` was ``None`` so this will ensure to have a valid - ``auth_key`` when needed, even if ``BrokenAuthKeyError`` is raised. -- Support for higher limits on ``.get_history()`` and - ``.get_dialogs()``. -- Much faster integer factorization when generating the required - ``auth_key``. Thanks @delivrance for making me notice this, and for - the pull request. - -Bug fixes with updates (v0.15.3) -================================ - -*Published at 2017/10/20* - -Hopefully a very ungrateful bug has been removed. When you used to -invoke some request through update handlers, it could potentially enter -an infinite loop. This has been mitigated and it's now safe to invoke -things again! A lot of updates were being dropped (all those gzipped), -and this has been fixed too. - -More bug fixes include a `correct -parsing `__ -of certain TLObjects thanks to @stek29, and -`some `__ -`wrong -calls `__ -that would cause the library to crash thanks to @andr-04, and the -``ReadThread`` not re-starting if you were already authorized. - -Internally, the ``.to_bytes()`` function has been replaced with -``__bytes__`` so now you can do ``bytes(tlobject)``. - -Bug fixes and new small features (v0.15.2) -========================================== - -*Published at 2017/10/14* - -This release primarly focuses on a few bug fixes and enhancements. -Although more stuff may have broken along the way. - -Enhancements -~~~~~~~~~~~~ - -- You will be warned if you call ``.add_update_handler`` with no - ``update_workers``. -- New customizable threshold value on the session to determine when to - automatically sleep on flood waits. See - ``client.session.flood_sleep_threshold``. -- New ``.get_drafts()`` method with a custom ``Draft`` class by @JosXa. -- Join all threads when calling ``.disconnect()``, to assert no - dangling thread is left alive. -- Larger chunk when downloading files should result in faster - downloads. -- You can use a callable key for the ``EntityDatabase``, so it can be - any filter you need. - -.. bug-fixes-3: - -Bug fixes -~~~~~~~~~ - -- ``.get_input_entity`` was failing for IDs and other cases, also - making more requests than it should. -- Use ``basename`` instead ``abspath`` when sending a file. You can now - also override the attributes. -- ``EntityDatabase.__delitem__`` wasn't working. -- ``.send_message()`` was failing with channels. -- ``.get_dialogs(limit=None)`` should now return all the dialogs - correctly. -- Temporary fix for abusive duplicated updates. - -.. enhancements-1: - -.. internal-changes-1: - -Internal changes -~~~~~~~~~~~~~~~~ - -- MsgsAck is now sent in a container rather than its own request. -- ``.get_input_photo`` is now used in the generated code. -- ``.process_entities`` was being called from more places than only - ``__call__``. -- ``MtProtoSender`` now relies more on the generated code to read - responses. - -Custom Entity Database (v0.15.1) -================================ - -*Published at 2017/10/05* - -The main feature of this release is that Telethon now has a custom -database for all the entities you encounter, instead depending on -``@lru_cache`` on the ``.get_entity()`` method. - -The ``EntityDatabase`` will, by default, **cache** all the users, chats -and channels you find in memory for as long as the program is running. -The session will, by default, save all key-value pairs of the entity -identifiers and their hashes (since Telegram may send an ID that it -thinks you already know about, we need to save this information). - -You can **prevent** the ``EntityDatabase`` from saving users by setting -``client.session.entities.enabled = False``, and prevent the ``Session`` -from saving input entities at all by setting -``client.session.save_entities = False``. You can also clear the cache -for a certain user through -``client.session.entities.clear_cache(entity=None)``, which will clear -all if no entity is given. - - -Additions -~~~~~~~~~ - -- New method to ``.delete_messages()``. -- New ``ChannelPrivateError`` class. - -Enhancements -~~~~~~~~~~~~ - -- ``.sign_in`` accepts phones as integers. -- Changing the IP to which you connect to is as simple as - ``client.session.server_address = 'ip'``, since now the - server address is always queried from the session. - -Bug fixes -~~~~~~~~~ - -- ``.get_dialogs()`` doesn't fail on Windows anymore, and returns the - right amount of dialogs. -- ``GeneralProxyError`` should be passed to the main thread - again, so that you can handle it. - -Updates Overhaul Update (v0.15) -=============================== - -*Published at 2017/10/01* - -After hundreds of lines changed on a major refactor, *it's finally -here*. It's the **Updates Overhaul Update**; let's get right into it! - -Breaking changes -~~~~~~~~~~~~~~~~ - -- ``.create_new_connection()`` is gone for good. No need to deal with - this manually since new connections are now handled on demand by the - library itself. - -Enhancements -~~~~~~~~~~~~ - -- You can **invoke** requests from **update handlers**. And **any other - thread**. A new temporary will be made, so that you can be sending - even several requests at the same time! -- **Several worker threads** for your updates! By default, ``None`` - will spawn. I recommend you to work with ``update_workers=4`` to get - started, these will be polling constantly for updates. -- You can also change the number of workers at any given time. -- The library can now run **in a single thread** again, if you don't - need to spawn any at all. Simply set ``spawn_read_thread=False`` when - creating the ``TelegramClient``! -- You can specify ``limit=None`` on ``.get_dialogs()`` to get **all** - of them[1]. -- **Updates are expanded**, so you don't need to check if the update - has ``.updates`` or an inner ``.update`` anymore. -- All ``InputPeer`` entities are **saved in the session** file, but you - can disable this by setting ``save_entities=False``. -- New ``.get_input_entity`` method, which makes use of the above - feature. You **should use this** when a request needs a - ``InputPeer``, rather than the whole entity (although both work). -- Assert that either all or None dependent-flag parameters are set - before sending the request. -- Phone numbers can have dashes, spaces, or parenthesis. They'll be - removed before making the request. -- You can override the phone and its hash on ``.sign_in()``, if you're - creating a new ``TelegramClient`` on two different places. - -Bug fixes -~~~~~~~~~ - -- ``.log_out()`` was consuming all retries. It should work just fine - now. -- The session would fail to load if the ``auth_key`` had been removed - manually. -- ``Updates.check_error`` was popping wrong side, although it's been - completely removed. -- ``ServerError``\ 's will be **ignored**, and the request will - immediately be retried. -- Cross-thread safety when saving the session file. -- Some things changed on a matter of when to reconnect, so please - report any bugs! - -.. internal-changes-2: - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``TelegramClient`` is now only an abstraction over the - ``TelegramBareClient``, which can only do basic things, such as - invoking requests, working with files, etc. If you don't need any of - the abstractions the ``TelegramClient``, you can now use the - ``TelegramBareClient`` in a much more comfortable way. -- ``MtProtoSender`` is not thread-safe, but it doesn't need to be since - a new connection will be spawned when needed. -- New connections used to be cached and then reused. Now only their - sessions are saved, as temporary connections are spawned only when - needed. -- Added more RPC errors to the list. - -**[1]:** Broken due to a condition which should had been the opposite -(sigh), fixed 4 commits ahead on -https://github.com/LonamiWebs/Telethon/commit/62ea77cbeac7c42bfac85aa8766a1b5b35e3a76c. - --------------- - -**That's pretty much it**, although there's more work to be done to make -the overall experience of working with updates *even better*. Stay -tuned! - -Serialization bug fixes (v0.14.2) -================================= - -*Published at 2017/09/29* - -Bug fixes -~~~~~~~~~ - -- **Important**, related to the serialization. Every object or request - that had to serialize a ``True/False`` type was always being serialized - as ``false``! -- Another bug that didn't allow you to leave as ``None`` flag parameters - that needed a list has been fixed. - -Internal changes -~~~~~~~~~~~~~~~~ - -- Other internal changes include a somewhat more readable ``.to_bytes()`` - function and pre-computing the flag instead using bit shifting. The - ``TLObject.constructor_id`` has been renamed to ``TLObject.CONSTRUCTOR_ID``, - and ``.subclass_of_id`` is also uppercase now. - -Farewell, BinaryWriter (v0.14.1) -================================ - -*Published at 2017/09/28* - -Version ``v0.14`` had started working on the new ``.to_bytes()`` method -to dump the ``BinaryWriter`` and its usage on the ``.on_send()`` when -serializing TLObjects, and this release finally removes it. The speed up -when serializing things to bytes should now be over twice as fast -wherever it's needed. - -Bug fixes -~~~~~~~~~ - -- This version is again compatible with Python 3.x versions **below 3.5** - (there was a method call that was Python 3.5 and above). - -Internal changes -~~~~~~~~~~~~~~~~ - -- Using proper classes (including the generated code) for generating - authorization keys and to write out ``TLMessage``\ 's. - - -Several requests at once and upload compression (v0.14) -======================================================= - -*Published at 2017/09/27* - -New major release, since I've decided that these two features are big -enough: - -Additions -~~~~~~~~~ - -- Requests larger than 512 bytes will be **compressed through - gzip**, and if the result is smaller, this will be uploaded instead. -- You can now send **multiple requests at once**, they're simply - ``*var_args`` on the ``.invoke()``. Note that the server doesn't - guarantee the order in which they'll be executed! - -Internally, another important change. The ``.on_send`` function on the -``TLObjects`` is **gone**, and now there's a new ``.to_bytes()``. From -my tests, this has always been over twice as fast serializing objects, -although more replacements need to be done, so please report any issues. - -Enhancements -~~~~~~~~~~~~ -- Implemented ``.get_input_media`` helper methods. Now you can even use - another message as input media! - - -Bug fixes -~~~~~~~~~ - -- Downloading media from CDNs wasn't working (wrong - access to a parameter). -- Correct type hinting. -- Added a tiny sleep when trying to perform automatic reconnection. -- Error reporting is done in the background, and has a shorter timeout. -- ``setup.py`` used to fail with wrongly generated code. - -Quick fix-up (v0.13.6) -====================== - -*Published at 2017/09/23* - -Before getting any further, here's a quick fix-up with things that -should have been on ``v0.13.5`` but were missed. Specifically, the -**timeout when receiving** a request will now work properly. - -Some other additions are a tiny fix when **handling updates**, which was -ignoring some of them, nicer ``__str__`` and ``.stringify()`` methods -for the ``TLObject``\ 's, and not stopping the ``ReadThread`` if you try -invoking something there (now it simply returns ``None``). - -Attempts at more stability (v0.13.5) -==================================== - -*Published at 2017/09/23* - -Yet another update to fix some bugs and increase the stability of the -library, or, at least, that was the attempt! - -This release should really **improve the experience with the background -thread** that the library starts to read things from the network as soon -as it can, but I can't spot every use case, so please report any bug -(and as always, minimal reproducible use cases will help a lot). - -.. bug-fixes-4: - -Bug fixes -~~~~~~~~~ - -- ``setup.py`` was failing on Python < 3.5 due to some imports. -- Duplicated updates should now be ignored. -- ``.send_message`` would crash in some cases, due to having a typo - using the wrong object. -- ``"socket is None"`` when calling ``.connect()`` should not happen - anymore. -- ``BrokenPipeError`` was still being raised due to an incorrect order - on the ``try/except`` block. - -.. enhancements-2: - -Enhancements -~~~~~~~~~~~~ - -- **Type hinting** for all the generated ``Request``\ 's and - ``TLObjects``! IDEs like PyCharm will benefit from this. -- ``ProxyConnectionError`` should properly be passed to the main thread - for you to handle. -- The background thread will only be started after you're authorized on - Telegram (i.e. logged in), and several other attempts at polishing - the experience with this thread. -- The ``Connection`` instance is only created once now, and reused - later. -- Calling ``.connect()`` should have a better behavior now (like - actually *trying* to connect even if we seemingly were connected - already). -- ``.reconnect()`` behavior has been changed to also be more consistent - by making the assumption that we'll only reconnect if the server has - disconnected us, and is now private. - -.. other-changes-1: - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``TLObject.__repr__`` doesn't show the original TL definition - anymore, it was a lot of clutter. If you have any complaints open an - issue and we can discuss it. -- Internally, the ``'+'`` from the phone number is now stripped, since - it shouldn't be included. -- Spotted a new place where ``BrokenAuthKeyError`` would be raised, and - it now is raised there. - -More bug fixes and enhancements (v0.13.4) -========================================= - -*Published at 2017/09/18* - -.. new-stuff-1: - -Additions -~~~~~~~~~ - -- ``TelegramClient`` now exposes a ``.is_connected()`` method. -- Initial authorization on a new data center will retry up to 5 times - by default. -- Errors that couldn't be handled on the background thread will be - raised on the next call to ``.invoke()`` or ``updates.poll()``. - -.. bugs-fixed-1: - -Bug fixes -~~~~~~~~~~ - -- Now you should be able to sign in even if you have - ``process_updates=True`` and no previous session. -- Some errors and methods are documented a bit clearer. -- ``.send_message()`` could randomly fail, as the returned type was not - expected. -- ``TimeoutError`` is now ignored, since the request will be retried up - to 5 times by default. -- "-404" errors (``BrokenAuthKeyError``\ 's) are now detected when - first connecting to a new data center. -- ``BufferError`` is handled more gracefully, in the same way as - ``InvalidCheckSumError``\ 's. -- Attempt at fixing some "NoneType has no attribute…" errors (with the - ``.sender``). - -Internal changes -~~~~~~~~~~~~~~~~ - -- Calling ``GetConfigRequest`` is now made less often. -- The ``initial_query`` parameter from ``.connect()`` is gone, as it's - not needed anymore. -- Renamed ``all_tlobjects.layer`` to ``all_tlobjects.LAYER`` (since - it's a constant). -- The message from ``BufferError`` is now more useful. - -Bug fixes and enhancements (v0.13.3) -==================================== - -*Published at 2017/09/14* - -.. bugs-fixed-2: - -Bug fixes -~~~~~~~~~ - -- **Reconnection** used to fail because it tried invoking things from - the ``ReadThread``. -- Inferring **random ids** for ``ForwardMessagesRequest`` wasn't - working. -- Downloading media from **CDNs** failed due to having forgotten to - remove a single line. -- ``TcpClient.close()`` now has a **``threading.Lock``**, so - ``NoneType has no close()`` should not happen. -- New **workaround** for ``msg seqno too low/high``. Also, both - ``Session.id/seq`` are not saved anymore. - -.. enhancements-3: - -Enhancements -~~~~~~~~~~~~ - -- **Request will be retried** up to 5 times by default rather than - failing on the first attempt. -- ``InvalidChecksumError``\ 's are now **ignored** by the library. -- ``TelegramClient.get_entity()`` is now **public**, and uses the - ``@lru_cache()`` decorator. -- New method to **``.send_voice_note()``**\ 's. -- Methods to send message and media now support a **``reply_to`` - parameter**. -- ``.send_message()`` now returns the **full message** which was just - sent. - -New way to work with updates (v0.13.2) -====================================== - -*Published at 2017/09/08* - -This update brings a new way to work with updates, and it's begging for -your **feedback**, or better names or ways to do what you can do now. - -Please refer to the `wiki/Usage -Modes `__ for -an in-depth description on how to work with updates now. Notice that you -cannot invoke requests from within handlers anymore, only the -``v.0.13.1`` patch allowed you to do so. - -Bug fixes -~~~~~~~~~ - -- Periodic pings are back. -- The username regex mentioned on ``UsernameInvalidError`` was invalid, - but it has now been fixed. -- Sending a message to a phone number was failing because the type used - for a request had changed on layer 71. -- CDN downloads weren't working properly, and now a few patches have been - applied to ensure more reliability, although I couldn't personally test - this, so again, report any feedback. - -Invoke other requests from within update callbacks (v0.13.1) -============================================================ - -*Published at 2017/09/04* - -.. warning:: - - This update brings some big changes to the update system, - so please read it if you work with them! - -A silly "bug" which hadn't been spotted has now been fixed. Now you can -invoke other requests from within your update callbacks. However **this -is not advised**. You should post these updates to some other thread, -and let that thread do the job instead. Invoking a request from within a -callback will mean that, while this request is being invoked, no other -things will be read. - -Internally, the generated code now resides under a *lot* less files, -simply for the sake of avoiding so many unnecessary files. The generated -code is not meant to be read by anyone, simply to do its job. - -Unused attributes have been removed from the ``TLObject`` class too, and -``.sign_up()`` returns the user that just logged in in a similar way to -``.sign_in()`` now. - -Connection modes (v0.13) -======================== - -*Published at 2017/09/04* - -+-----------------------+ -| Scheme layer used: 71 | -+-----------------------+ - -The purpose of this release is to denote a big change, now you can -connect to Telegram through different `**connection -modes** `__. -Also, a **second thread** will *always* be started when you connect a -``TelegramClient``, despite whether you'll be handling updates or -ignoring them, whose sole purpose is to constantly read from the -network. - -The reason for this change is as simple as *"reading and writing -shouldn't be related"*. Even when you're simply ignoring updates, this -way, once you send a request you will only need to read the result for -the request. Whatever Telegram sent before has already been read and -outside the buffer. - -.. additions-2: - -Additions -~~~~~~~~~ - -- The mentioned different connection modes, and a new thread. -- You can modify the ``Session`` attributes through the - ``TelegramClient`` constructor (using ``**kwargs``). -- ``RPCError``\ 's now belong to some request you've made, which makes - more sense. -- ``get_input_*`` now handles ``None`` (default) parameters more - gracefully (it used to crash). - -.. enhancements-4: - -Enhancements -~~~~~~~~~~~~ - -- The low-level socket doesn't use a handcrafted timeout anymore, which - should benefit by avoiding the arbitrary ``sleep(0.1)`` that there - used to be. -- ``TelegramClient.sign_in`` will call ``.send_code_request`` if no - ``code`` was provided. - -Deprecation -~~~~~~~~~~~ - -- ``.sign_up`` does *not* take a ``phone`` argument anymore. Change - this or you will be using ``phone`` as ``code``, and it will fail! - The definition looks like - ``def sign_up(self, code, first_name, last_name='')``. -- The old ``JsonSession`` finally replaces the original ``Session`` - (which used pickle). If you were overriding any of these, you should - only worry about overriding ``Session`` now. - -Added verification for CDN file (v0.12.2) -========================================= - -*Published at 2017/08/28* - -Since the Content Distributed Network (CDN) is not handled by Telegram -itself, the owners may tamper these files. Telegram sends their sha256 -sum for clients to implement this additional verification step, which -now the library has. If any CDN has altered the file you're trying to -download, ``CdnFileTamperedError`` will be raised to let you know. - -Besides this. ``TLObject.stringify()`` was showing bytes as lists (now -fixed) and RPC errors are reported by default: - - In an attempt to help everyone who works with the Telegram API, - Telethon will by default report all Remote Procedure Call errors to - `PWRTelegram `__, a public database anyone can - query, made by `Daniil `__. All the information - sent is a GET request with the error code, error message and method used. - - -.. note:: - - If you still would like to opt out, simply set - ``client.session.report_errors = False`` to disable this feature. - However Daniil would really thank you if you helped him (and everyone) - by keeping it on! - -CDN support (v0.12.1) -===================== - -*Published at 2017/08/24* - -The biggest news for this update are that downloading media from CDN's -(you'll often encounter this when working with popular channels) now -**works**. - -Bug fixes -~~~~~~~~~ - -- The method used to download documents crashed because - two lines were swapped. -- Determining the right path when downloading any file was - very weird, now it's been enhanced. -- The ``.sign_in()`` method didn't support integer values for the code! - Now it does again. - -Some important internal changes are that the old way to deal with RSA -public keys now uses a different module instead the old strange -hand-crafted version. - -Hope the new, super simple ``README.rst`` encourages people to use -Telethon and make it better with either suggestions, or pull request. -Pull requests are *super* appreciated, but showing some support by -leaving a star also feels nice ⭐️. - -Newbie friendly update (v0.12) -============================== - -*Published at 2017/08/22* - -+-----------------------+ -| Scheme layer used: 70 | -+-----------------------+ - -This update is overall an attempt to make Telethon a bit more user -friendly, along with some other stability enhancements, although it -brings quite a few changes. - -Breaking changes -~~~~~~~~~~~~~~~~ - -- The ``TelegramClient`` methods ``.send_photo_file()``, - ``.send_document_file()`` and ``.send_media_file()`` are now a - **single method** called ``.send_file()``. It's also important to - note that the **order** of the parameters has been **swapped**: first - to *who* you want to send it, then the file itself. - -- The same applies to ``.download_msg_media()``, which has been renamed - to ``.download_media()``. The method now supports a ``Message`` - itself too, rather than only ``Message.media``. The specialized - ``.download_photo()``, ``.download_document()`` and - ``.download_contact()`` still exist, but are private. - -Additions -~~~~~~~~~ - -- Updated to **layer 70**! -- Both downloading and uploading now support **stream-like objects**. -- A lot **faster initial connection** if ``sympy`` is installed (can be - installed through ``pip``). -- ``libssl`` will also be used if available on your system (likely on - Linux based systems). This speed boost should also apply to uploading - and downloading files. -- You can use a **phone number** or an **username** for methods like - ``.send_message()``, ``.send_file()``, and all the other quick-access - methods provided by the ``TelegramClient``. - -.. bug-fixes-5: - -Bug fixes -~~~~~~~~~ - -- Crashing when migrating to a new layer and receiving old updates - should not happen now. -- ``InputPeerChannel`` is now casted to ``InputChannel`` automtically - too. -- ``.get_new_msg_id()`` should now be thread-safe. No promises. -- Logging out on macOS caused a crash, which should be gone now. -- More checks to ensure that the connection is flagged correctly as - either connected or not. - -.. note:: - - Downloading files from CDN's will **not work** yet (something new - that comes with layer 70). - --------------- - -That's it, any new idea or suggestion about how to make the project even -more friendly is highly appreciated. - -.. note:: - - Did you know that you can pretty print any result Telegram returns - (called ``TLObject``\ 's) by using their ``.stringify()`` function? - Great for debugging! - -get_input_* now works with vectors (v0.11.5) -============================================= - -*Published at 2017/07/11* - -Quick fix-up of a bug which hadn't been encountered until now. Auto-cast -by using ``get_input_*`` now works. - -get_input_* everywhere (v0.11.4) -================================= - -*Published at 2017/07/10* - -For some reason, Telegram doesn't have enough with the -`InputPeer `__. -There also exist -`InputChannel `__ -and -`InputUser `__! -You don't have to worry about those anymore, it's handled internally -now. - -Besides this, every Telegram object now features a new default -``.__str__`` look, and also a `.stringify() -method `__ -to pretty format them, if you ever need to inspect them. - -The library now uses `the DEBUG -level `__ -everywhere, so no more warnings or information messages if you had -logging enabled. - -The ``no_webpage`` parameter from ``.send_message`` `has been -renamed `__ -to ``link_preview`` for clarity, so now it does the opposite (but has a -clearer intention). - -Quick .send_message() fix (v0.11.3) -=================================== - -*Published at 2017/07/05* - -A very quick follow-up release to fix a tiny bug with -``.send_message()``, no new features. - -Callable TelegramClient (v0.11.2) -================================= - -*Published at 2017/07/04* - -+-----------------------+ -| Scheme layer used: 68 | -+-----------------------+ - -There is a new preferred way to **invoke requests**, which you're -encouraged to use: - -.. code:: python - - # New! - result = client(SomeRequest()) - - # Old. - result = client.invoke(SomeRequest()) - -Existing code will continue working, since the old ``.invoke()`` has not -been deprecated. - -When you ``.create_new_connection()``, it will also handle -``FileMigrateError``\ 's for you, so you don't need to worry about those -anymore. - -.. bugs-fixed-3: - -Bugs fixes -~~~~~~~~~~ - -- Fixed some errors when installing Telethon via ``pip`` (for those - using either source distributions or a Python version ≤ 3.5). -- ``ConnectionResetError`` didn't flag sockets as closed, but now it - does. - -On a more technical side, ``msg_id``\ 's are now more accurate. - -Improvements to the updates (v0.11.1) -===================================== - -*Published at 2017/06/24* - -Receiving new updates shouldn't miss any anymore, also, periodic pings -are back again so it should work on the long run. - -On a different order of things, ``.connect()`` also features a timeout. -Notice that the ``timeout=`` is **not** passed as a **parameter** -anymore, and is instead specified when creating the ``TelegramClient``. - -Bug fixes -~~~~~~~~~ - -- Fixed some name class when a request had a ``.msg_id`` parameter. -- The correct amount of random bytes is now used in DH request -- Fixed ``CONNECTION_APP_VERSION_EMPTY`` when using temporary sessions. -- Avoid connecting if already connected. - -Support for parallel connections (v0.11) -======================================== - -*Published at 2017/06/16* - -*This update brings a lot of changes, so it would be nice if you could* -**read the whole change log**! - -Breaking changes -~~~~~~~~~~~~~~~~ - -- Every Telegram error has now its **own class**, so it's easier to - fine-tune your ``except``\ 's. -- Markdown parsing is **not part** of Telethon itself anymore, although - there are plans to support it again through a some external module. -- The ``.list_sessions()`` has been moved to the ``Session`` class - instead. -- The ``InteractiveTelegramClient`` is **not** shipped with ``pip`` - anymore. - -Additions -~~~~~~~~~ - -- A new, more **lightweight class** has been added. The - ``TelegramBareClient`` is now the base of the normal - ``TelegramClient``, and has the most basic features. -- New method to ``.create_new_connection()``, which can be ran **in - parallel** with the original connection. This will return the - previously mentioned ``TelegramBareClient`` already connected. -- Any file object can now be used to download a file (for instance, a - ``BytesIO()`` instead a file name). -- Vales like ``random_id`` are now **automatically inferred**, so you - can save yourself from the hassle of writing - ``generate_random_long()`` everywhere. Same applies to - ``.get_input_peer()``, unless you really need the extra performance - provided by skipping one ``if`` if called manually. -- Every type now features a new ``.to_dict()`` method. - -.. bug-fixes-6: - -Bug fixes -~~~~~~~~~ - -- Received errors are acknowledged to the server, so they don't happen - over and over. -- Downloading media on different data centers is now up to **x2 - faster**, since there used to be an ``InvalidDCError`` for each file - part tried to be downloaded. -- Lost messages are now properly skipped. -- New way to handle the **result of requests**. The old ``ValueError`` - "*The previously sent request must be resent. However, no request was - previously sent (possibly called from a different thread).*" *should* - not happen anymore. - -Internal changes -~~~~~~~~~~~~~~~~ - -- Some fixes to the ``JsonSession``. -- Fixed possibly crashes if trying to ``.invoke()`` a ``Request`` while - ``.reconnect()`` was being called on the ``UpdatesThread``. -- Some improvements on the ``TcpClient``, such as not switching between - blocking and non-blocking sockets. -- The code now uses ASCII characters only. -- Some enhancements to ``.find_user_or_chat()`` and - ``.get_input_peer()``. - -JSON session file (v0.10.1) -=========================== - -*Published at 2017/06/07* - -This version is primarily for people to **migrate** their ``.session`` -files, which are *pickled*, to the new *JSON* format. Although slightly -slower, and a bit more vulnerable since it's plain text, it's a lot more -resistant to upgrades. - -.. warning:: - - You **must** upgrade to this version before any higher one if you've - used Telethon ≤ v0.10. If you happen to upgrade to an higher version, - that's okay, but you will have to manually delete the ``*.session`` file, - and logout from that session from an official client. - -Additions -~~~~~~~~~ - -- New ``.get_me()`` function to get the **current** user. -- ``.is_user_authorized()`` is now more reliable. -- New nice button to copy the ``from telethon.tl.xxx.yyy import Yyy`` - on the online documentation. -- **More error codes** added to the ``errors`` file. - -Enhancements -~~~~~~~~~~~~ - -- Everything on the documentation is now, theoretically, **sorted - alphabetically**. -- No second thread is spawned unless one or more update handlers are added. - -Full support for different DCs and ++stable (v0.10) -=================================================== - -*Published at 2017/06/03* - -Working with **different data centers** finally *works*! On a different -order of things, **reconnection** is now performed automatically every -time Telegram decides to kick us off their servers, so now Telethon can -really run **forever and ever**! In theory. - -Enhancements -~~~~~~~~~~~~ - -- **Documentation** improvements, such as showing the return type. -- The ``msg_id too low/high`` error should happen **less often**, if - any. -- Sleeping on the main thread is **not done anymore**. You will have to - ``except FloodWaitError``\ 's. -- You can now specify your *own application version*, device model, - system version and language code. -- Code is now more *pythonic* (such as making some members private), - and other internal improvements (which affect the **updates - thread**), such as using ``logger`` instead a bare ``print()`` too. - -This brings Telethon a whole step closer to ``v1.0``, though more things -should preferably be changed. - -Stability improvements (v0.9.1) -=============================== - -*Published at 2017/05/23* - -Telethon used to crash a lot when logging in for the very first time. -The reason for this was that the reconnection (or dead connections) were -not handled properly. Now they are, so you should be able to login -directly, without needing to delete the ``*.session`` file anymore. -Notice that downloading from a different DC is still a WIP. - -Enhancements -~~~~~~~~~~~~ - -- Updates thread is only started after a successful login. -- Files meant to be ran by the user now use **shebangs** and - proper permissions. -- In-code documentation now shows the returning type. -- **Relative import** is now used everywhere, so you can rename - ``telethon`` to anything else. -- **Dead connections** are now **detected** instead entering an infinite loop. -- **Sockets** can now be **closed** (and re-opened) properly. -- Telegram decided to update the layer 66 without increasing the number. - This has been fixed and now we're up-to-date again. - -General improvements (v0.9) -=========================== - -*Published at 2017/05/19* - -+-----------------------+ -| Scheme layer used: 66 | -+-----------------------+ - -Additions -~~~~~~~~~ - -- The **documentation**, available online - `here `__, has a new search bar. -- Better **cross-thread safety** by using ``threading.Event``. -- More improvements for running Telethon during a **long period of time**. - -Bug fixes -~~~~~~~~~ - -- **Avoid a certain crash on login** (occurred if an unexpected object - ID was received). -- Avoid crashing with certain invalid UTF-8 strings. -- Avoid crashing on certain terminals by using known ASCII characters - where possible. -- The ``UpdatesThread`` is now a daemon, and should cause less issues. -- Temporary sessions didn't actually work (with ``session=None``). - -Internal changes -~~~~~~~~~~~~~~~~ - -- ``.get_dialogs(count=`` was renamed to ``.get_dialogs(limit=``. - -Bot login and proxy support (v0.8) -================================== - -*Published at 2017/04/14* - -Additions -~~~~~~~~~ - -- **Bot login**, thanks to @JuanPotato for hinting me about how to do - it. -- **Proxy support**, thanks to @exzhawk for implementing it. -- **Logging support**, used by passing ``--telethon-log=DEBUG`` (or - ``INFO``) as a command line argument. - -Bug fixes -~~~~~~~~~ - -- Connection fixes, such as avoiding connection until ``.connect()`` is - explicitly invoked. -- Uploading big files now works correctly. -- Fix uploading big files. -- Some fixes on the updates thread, such as correctly sleeping when required. - -Long-run bug fix (v0.7.1) -========================= - -*Published at 2017/02/19* - -If you're one of those who runs Telethon for a long time (more than 30 -minutes), this update by @strayge will be great for you. It sends -periodic pings to the Telegram servers so you don't get disconnected and -you can still send and receive updates! - -Two factor authentication (v0.7) -================================ - -*Published at 2017/01/31* - -+-----------------------+ -| Scheme layer used: 62 | -+-----------------------+ - -If you're one of those who love security the most, these are good news. -You can now use two factor authentication with Telethon too! As internal -changes, the coding style has been improved, and you can easily use -custom session objects, and various little bugs have been fixed. - -Updated pip version (v0.6) -========================== - -*Published at 2016/11/13* - -+-----------------------+ -| Scheme layer used: 57 | -+-----------------------+ - -This release has no new major features. However, it contains some small -changes that make using Telethon a little bit easier. Now those who have -installed Telethon via ``pip`` can also take advantage of changes, such -as less bugs, creating empty instances of ``TLObjects``, specifying a -timeout and more! - -Ready, pip, go! (v0.5) -====================== - -*Published at 2016/09/18* - -Telethon is now available as a **`Python -package `__**! Those are -really exciting news (except, sadly, the project structure had to change -*a lot* to be able to do that; but hopefully it won't need to change -much more, any more!) - -Not only that, but more improvements have also been made: you're now -able to both **sign up** and **logout**, watch a pretty -"Uploading/Downloading… x%" progress, and other minor changes which make -using Telethon **easier**. - -Made InteractiveTelegramClient cool (v0.4) -========================================== - -*Published at 2016/09/12* - -Yes, really cool! I promise. Even though this is meant to be a -*library*, that doesn't mean it can't have a good *interactive client* -for you to try the library out. This is why now you can do many, many -things with the ``InteractiveTelegramClient``: - -- **List dialogs** (chats) and pick any you wish. -- **Send any message** you like, text, photos or even documents. -- **List** the **latest messages** in the chat. -- **Download** any message's media (photos, documents or even contacts!). -- **Receive message updates** as you talk (i.e., someone sent you a message). - -It actually is an usable-enough client for your day by day. You could -even add ``libnotify`` and pop, you're done! A great cli-client with -desktop notifications. - -Also, being able to download and upload media implies that you can do -the same with the library itself. Did I need to mention that? Oh, and -now, with even less bugs! I hope. - -Media revolution and improvements to update handling! (v0.3) -============================================================ - -*Published at 2016/09/11* - -Telegram is more than an application to send and receive messages. You -can also **send and receive media**. Now, this implementation also gives -you the power to upload and download media from any message that -contains it! Nothing can now stop you from filling up all your disk -space with all the photos! If you want to, of course. - -Handle updates in their own thread! (v0.2) -========================================== - -*Published at 2016/09/10* - -This version handles **updates in a different thread** (if you wish to -do so). This means that both the low level ``TcpClient`` and the -not-so-low-level ``MtProtoSender`` are now multi-thread safe, so you can -use them with more than a single thread without worrying! - -This also implies that you won't need to send a request to **receive an -update** (is someone typing? did they send me a message? has someone -gone offline?). They will all be received **instantly**. - -Some other cool examples of things that you can do: when someone tells -you "*Hello*", you can automatically reply with another "*Hello*" -without even needing to type it by yourself :) - -However, be careful with spamming!! Do **not** use the program for that! - -First working alpha version! (v0.1) -=================================== - -*Published at 2016/09/06* - -+-----------------------+ -| Scheme layer used: 55 | -+-----------------------+ - -There probably are some bugs left, which haven't yet been found. -However, the majority of code works and the application is already -usable! Not only that, but also uses the latest scheme as of now *and* -handles way better the errors. This tag is being used to mark this -release as stable enough. diff --git a/readthedocs/extra/developing/api-status.rst b/readthedocs/extra/developing/api-status.rst deleted file mode 100644 index e113c48e..00000000 --- a/readthedocs/extra/developing/api-status.rst +++ /dev/null @@ -1,54 +0,0 @@ -.. _api-status: - -========== -API Status -========== - - -In an attempt to help everyone who works with the Telegram API, the -library will by default report all *Remote Procedure Call* errors to -`RPC PWRTelegram `__, a public database -anyone can query, made by `Daniil `__. All the -information sent is a ``GET`` request with the error code, error message -and method used. - -If you still would like to opt out, you can disable this feature by setting -``client.session.report_errors = False``. However Daniil would really thank -you if you helped him (and everyone) by keeping it on! - -Querying the API status -*********************** - -The API is accessed through ``GET`` requests, which can be made for -instance through ``curl``. A JSON response will be returned. - -**All known errors and their description**: - -.. code:: bash - - curl https://rpc.pwrtelegram.xyz/?all - -**Error codes for a specific request**: - -.. code:: bash - - curl https://rpc.pwrtelegram.xyz/?for=messages.sendMessage - -**Number of** ``RPC_CALL_FAIL``: - -.. code:: bash - - curl https://rpc.pwrtelegram.xyz/?rip # last hour - curl https://rpc.pwrtelegram.xyz/?rip=$(time()-60) # last minute - -**Description of errors**: - -.. code:: bash - - curl https://rpc.pwrtelegram.xyz/?description_for=SESSION_REVOKED - -**Code of a specific error**: - -.. code:: bash - - curl https://rpc.pwrtelegram.xyz/?code_for=STICKERSET_INVALID diff --git a/readthedocs/extra/developing/coding-style.rst b/readthedocs/extra/developing/coding-style.rst deleted file mode 100644 index c629034c..00000000 --- a/readthedocs/extra/developing/coding-style.rst +++ /dev/null @@ -1,22 +0,0 @@ -============ -Coding Style -============ - - -Basically, make it **readable**, while keeping the style similar to the -code of whatever file you're working on. - -Also note that not everyone has 4K screens for their primary monitors, -so please try to stick to the 80-columns limit. This makes it easy to -``git diff`` changes from a terminal before committing changes. If the -line has to be long, please don't exceed 120 characters. - -For the commit messages, please make them *explanatory*. Not only -they're helpful to troubleshoot when certain issues could have been -introduced, but they're also used to construct the change log once a new -version is ready. - -If you don't know enough Python, I strongly recommend reading `Dive Into -Python 3 `__, available online for -free. For instance, remember to do ``if x is None`` or -``if x is not None`` instead ``if x == None``! diff --git a/readthedocs/extra/developing/philosophy.rst b/readthedocs/extra/developing/philosophy.rst deleted file mode 100644 index f779be2b..00000000 --- a/readthedocs/extra/developing/philosophy.rst +++ /dev/null @@ -1,25 +0,0 @@ -========== -Philosophy -========== - - -The intention of the library is to have an existing MTProto library -existing with hardly any dependencies (indeed, wherever Python is -available, you can run this library). - -Being written in Python means that performance will be nowhere close to -other implementations written in, for instance, Java, C++, Rust, or -pretty much any other compiled language. However, the library turns out -to actually be pretty decent for common operations such as sending -messages, receiving updates, or other scripting. Uploading files may be -notably slower, but if you would like to contribute, pull requests are -appreciated! - -If ``libssl`` is available on your system, the library will make use of -it to speed up some critical parts such as encrypting and decrypting the -messages. Files will notably be sent and downloaded faster. - -The main focus is to keep everything clean and simple, for everyone to -understand how working with MTProto and Telegram works. Don't be afraid -to read the source, the code won't bite you! It may prove useful when -using the library on your own use cases. diff --git a/readthedocs/extra/developing/project-structure.rst b/readthedocs/extra/developing/project-structure.rst deleted file mode 100644 index 1bbb07c4..00000000 --- a/readthedocs/extra/developing/project-structure.rst +++ /dev/null @@ -1,52 +0,0 @@ -================= -Project Structure -================= - - -Main interface -************** - -The library itself is under the ``telethon/`` directory. The -``__init__.py`` file there exposes the main ``TelegramClient``, a class -that servers as a nice interface with the most commonly used methods on -Telegram such as sending messages, retrieving the message history, -handling updates, etc. - -The ``TelegramClient`` inherits from several mixing ``Method`` classes, -since there are so many methods that having them in a single file would -make maintenance painful (it was three thousand lines before this separation -happened!). It's a "god object", but there is only a way to interact with -Telegram really. - -The ``TelegramBaseClient`` is an ABC which will support all of these mixins -so they can work together nicely. It doesn't even know how to invoke things -because they need to be resolved with user information first (to work with -input entities comfortably). - -The client makes use of the ``network/mtprotosender.py``. The -``MTProtoSender`` is responsible for connecting, reconnecting, -packing, unpacking, sending and receiving items from the network. -Basically, the low-level communication with Telegram, and handling -MTProto-related functions and types such as ``BadSalt``. - -The sender makes use of a ``Connection`` class which knows the format in -which outgoing messages should be sent (how to encode their length and -their body, if they're further encrypted). - -For now, all connection modes make use of the ``extensions/tcpclient``, -a C#-like ``TcpClient`` to ease working with sockets in Python. All the -``TcpClient`` know is how to connect through TCP and writing/reading -from the socket with optional cancel. - -Auto-generated code -******************* - -The files under ``telethon_generator/`` are used to generate the code -that gets placed under ``telethon/tl/``. The parsers take in files in -a specific format (such as ``.tl`` for objects and ``.json`` for errors) -and spit out the generated classes which represent, as Python classes, -the request and types defined in the ``.tl`` file. It also constructs -an index so that they can be imported easily. - -Custom documentation can also be generated to easily navigate through -the vast amount of items offered by the API. diff --git a/readthedocs/extra/developing/telegram-api-in-other-languages.rst b/readthedocs/extra/developing/telegram-api-in-other-languages.rst deleted file mode 100644 index 22bb416a..00000000 --- a/readthedocs/extra/developing/telegram-api-in-other-languages.rst +++ /dev/null @@ -1,73 +0,0 @@ -=============================== -Telegram API in Other Languages -=============================== - - -Telethon was made for **Python**, and as far as I know, there is no -*exact* port to other languages. However, there *are* other -implementations made by awesome people (one needs to be awesome to -understand the official Telegram documentation) on several languages -(even more Python too), listed below: - -C -* - -Possibly the most well-known unofficial open source implementation out -there by `@vysheng `__, -`tgl `__, and its console client -`telegram-cli `__. Latest development -has been moved to `BitBucket `__. - -C++ -*** - -The newest (and official) library, written from scratch, is called -`tdlib `__ and is what the Telegram X -uses. You can find more information in the official documentation, -published `here `__. - -JavaScript -********** - -`@zerobias `__ is working on -`telegram-mtproto `__, -a work-in-progress JavaScript library installable via -`npm `__. - -Kotlin -****** - -`Kotlogram `__ is a Telegram -implementation written in Kotlin (one of the -`official `__ -languages for -`Android `__) by -`@badoualy `__, currently as a beta– -yet working. - -PHP -*** - -A PHP implementation is also available thanks to -`@danog `__ and his -`MadelineProto `__ project, with -a very nice `online -documentation `__ too. - -Python -****** - -A fairly new (as of the end of 2017) Telegram library written from the -ground up in Python by -`@delivrance `__ and his -`Pyrogram `__ library. -There isn't really a reason to pick it over Telethon and it'd be kinda -sad to see you go, but it would be nice to know what you miss from each -other library in either one so both can improve. - -Rust -**** - -Yet another work-in-progress implementation, this time for Rust thanks -to `@JuanPotato `__ under the fancy -name of `Vail `__. diff --git a/readthedocs/extra/developing/test-servers.rst b/readthedocs/extra/developing/test-servers.rst deleted file mode 100644 index bf8fd97e..00000000 --- a/readthedocs/extra/developing/test-servers.rst +++ /dev/null @@ -1,37 +0,0 @@ -============ -Test Servers -============ - - -To run Telethon on a test server, use the following code: - -.. code-block:: python - - client = TelegramClient(None, api_id, api_hash) - client.session.set_dc(dc_id, '149.154.167.40', 80) - -You can check your ``'test ip'`` on https://my.telegram.org. - -You should set ``None`` session so to ensure you're generating a new -authorization key for it (it would fail if you used a session where you -had previously connected to another data center). - -Note that port 443 might not work, so you can try with 80 instead. - -Once you're connected, you'll likely be asked to either sign in or sign up. -Remember `anyone can access the phone you -choose `__, -so don't store sensitive data here. - -Valid phone numbers are ``99966XYYYY``, where ``X`` is the ``dc_id`` and -``YYYY`` is any number you want, for example, ``1234`` in ``dc_id = 2`` would -be ``9996621234``. The code sent by Telegram will be ``dc_id`` repeated five -times, in this case, ``22222`` so we can hardcode that: - -.. code-block:: python - - client = TelegramClient(None, api_id, api_hash) - client.session.set_dc(2, '149.154.167.40', 80) - loop.run_until_complete(client.start( - phone='9996621234', code_callback=lambda: '22222' - )) diff --git a/readthedocs/extra/developing/tips-for-porting-the-project.rst b/readthedocs/extra/developing/tips-for-porting-the-project.rst deleted file mode 100644 index 69348f9d..00000000 --- a/readthedocs/extra/developing/tips-for-porting-the-project.rst +++ /dev/null @@ -1,17 +0,0 @@ -============================ -Tips for Porting the Project -============================ - - -If you're going to use the code on this repository to guide you, please -be kind and don't forget to mention it helped you! - -You should start by reading the source code on the `first -release `__ of -the project, and start creating a ``MTProtoSender``. Once this is made, -you should write by hand the code to authenticate on the Telegram's -server, which are some steps required to get the key required to talk to -them. Save it somewhere! Then, simply mimic, or reinvent other parts of -the code, and it will be ready to go within a few days. - -Good luck! diff --git a/readthedocs/extra/developing/understanding-the-type-language.rst b/readthedocs/extra/developing/understanding-the-type-language.rst deleted file mode 100644 index 8e5259a7..00000000 --- a/readthedocs/extra/developing/understanding-the-type-language.rst +++ /dev/null @@ -1,33 +0,0 @@ -=============================== -Understanding the Type Language -=============================== - - -`Telegram's Type Language `__ -(also known as TL, found on ``.tl`` files) is a concise way to define -what other programming languages commonly call classes or structs. - -Every definition is written as follows for a Telegram object is defined -as follows: - - ``name#id argument_name:argument_type = CommonType`` - -This means that in a single line you know what the ``TLObject`` name is. -You know it's unique ID, and you know what arguments it has. It really -isn't that hard to write a generator for generating code to any -platform! - -The generated code should also be able to *encode* the ``TLObject`` (let -this be a request or a type) into bytes, so they can be sent over the -network. This isn't a big deal either, because you know how the -``TLObject``\ 's are made, and how the types should be serialized. - -You can either write your own code generator, or use the one this -library provides, but please be kind and keep some special mention to -this project for helping you out. - -This is only a introduction. The ``TL`` language is not *that* easy. But -it's not that hard either. You're free to sniff the -``telethon_generator/`` files and learn how to parse other more complex -lines, such as ``flags`` (to indicate things that may or may not be -written at all) and ``vector``\ 's. diff --git a/readthedocs/extra/examples/bots.rst b/readthedocs/extra/examples/bots.rst deleted file mode 100644 index 20bb26d3..00000000 --- a/readthedocs/extra/examples/bots.rst +++ /dev/null @@ -1,71 +0,0 @@ -==== -Bots -==== - - -.. note:: - - These examples assume you have read :ref:`accessing-the-full-api`. - - -Talking to Inline Bots -********************** - -You can query an inline bot, such as `@VoteBot`__ (note, *query*, -not *interact* with a voting message), by making use of the -:tl:`GetInlineBotResultsRequest` request: - -.. code-block:: python - - from telethon.tl.functions.messages import GetInlineBotResultsRequest - - bot_results = client(GetInlineBotResultsRequest( - bot, user_or_chat, 'query', '' - )) - -And you can select any of their results by using -:tl:`SendInlineBotResultRequest`: - -.. code-block:: python - - from telethon.tl.functions.messages import SendInlineBotResultRequest - - client(SendInlineBotResultRequest( - get_input_peer(user_or_chat), - obtained_query_id, - obtained_str_id - )) - - -Talking to Bots with special reply markup -***************************************** - -Generally, you just use the `message.click() -` method: - -.. code-block:: python - - messages = client.get_messages('somebot') - messages[0].click(0) - -You can also do it manually. - -To interact with a message that has a special reply markup, such as -`@VoteBot`__ polls, you would use :tl:`GetBotCallbackAnswerRequest`: - -.. code-block:: python - - from telethon.tl.functions.messages import GetBotCallbackAnswerRequest - - client(GetBotCallbackAnswerRequest( - user_or_chat, - msg.id, - data=msg.reply_markup.rows[wanted_row].buttons[wanted_button].data - )) - -It's a bit verbose, but it has all the information you would need to -show it visually (button rows, and buttons within each row, each with -its own data). - -__ https://t.me/vote -__ https://t.me/vote diff --git a/readthedocs/extra/examples/chats-and-channels.rst b/readthedocs/extra/examples/chats-and-channels.rst deleted file mode 100644 index 3b928e8c..00000000 --- a/readthedocs/extra/examples/chats-and-channels.rst +++ /dev/null @@ -1,318 +0,0 @@ -=============================== -Working with Chats and Channels -=============================== - - -.. note:: - - These examples assume you have read :ref:`accessing-the-full-api`. - - -Joining a chat or channel -************************* - -Note that :tl:`Chat` are normal groups, and :tl:`Channel` are a -special form of :tl:`Chat`, which can also be super-groups if -their ``megagroup`` member is ``True``. - - -Joining a public channel -************************ - -Once you have the :ref:`entity ` of the channel you want to join -to, you can make use of the :tl:`JoinChannelRequest` to join such channel: - -.. code-block:: python - - from telethon.tl.functions.channels import JoinChannelRequest - client(JoinChannelRequest(channel)) - - # In the same way, you can also leave such channel - from telethon.tl.functions.channels import LeaveChannelRequest - client(LeaveChannelRequest(input_channel)) - - -For more on channels, check the `channels namespace`__. - - -__ https://lonamiwebs.github.io/Telethon/methods/channels/index.html - - -Joining a private chat or channel -********************************* - -If all you have is a link like this one: -``https://t.me/joinchat/AAAAAFFszQPyPEZ7wgxLtd``, you already have -enough information to join! The part after the -``https://t.me/joinchat/``, this is, ``AAAAAFFszQPyPEZ7wgxLtd`` on this -example, is the ``hash`` of the chat or channel. Now you can use -:tl:`ImportChatInviteRequest` as follows: - -.. code-block:: python - - from telethon.tl.functions.messages import ImportChatInviteRequest - updates = client(ImportChatInviteRequest('AAAAAEHbEkejzxUjAUCfYg')) - - -Adding someone else to such chat or channel -******************************************* - -If you don't want to add yourself, maybe because you're already in, -you can always add someone else with the :tl:`AddChatUserRequest`, which -use is very straightforward, or :tl:`InviteToChannelRequest` for channels: - -.. code-block:: python - - # For normal chats - from telethon.tl.functions.messages import AddChatUserRequest - - # Note that ``user_to_add`` is NOT the name of the parameter. - # It's the user you want to add (``user_id=user_to_add``). - client(AddChatUserRequest( - chat_id, - user_to_add, - fwd_limit=10 # Allow the user to see the 10 last messages - )) - - # For channels (which includes megagroups) - from telethon.tl.functions.channels import InviteToChannelRequest - - client(InviteToChannelRequest( - channel, - [users_to_add] - )) - - -Checking a link without joining -******************************* - -If you don't need to join but rather check whether it's a group or a -channel, you can use the :tl:`CheckChatInviteRequest`, which takes in -the hash of said channel or group. - - -Retrieving all chat members (channels too) -****************************************** - -.. note:: - - Use the `telethon.telegram_client.TelegramClient.iter_participants` - friendly method instead unless you have a better reason not to! - - This method will handle different chat types for you automatically. - - -Here is the easy way to do it: - -.. code-block:: python - - participants = client.get_participants(group) - -Now we will show how the method works internally. - -In order to get all the members from a mega-group or channel, you need -to use :tl:`GetParticipantsRequest`. As we can see it needs an -:tl:`InputChannel`, (passing the mega-group or channel you're going to -use will work), and a mandatory :tl:`ChannelParticipantsFilter`. The -closest thing to "no filter" is to simply use -:tl:`ChannelParticipantsSearch` with an empty ``'q'`` string. - -If we want to get *all* the members, we need to use a moving offset and -a fixed limit: - -.. code-block:: python - - from telethon.tl.functions.channels import GetParticipantsRequest - from telethon.tl.types import ChannelParticipantsSearch - from time import sleep - - offset = 0 - limit = 100 - all_participants = [] - - while True: - participants = client(GetParticipantsRequest( - channel, ChannelParticipantsSearch(''), offset, limit, hash=0 - )) - if not participants.users: - break - all_participants.extend(participants.users) - offset += len(participants.users) - - -.. note:: - - If you need more than 10,000 members from a group you should use the - mentioned ``client.get_participants(..., aggressive=True)``. It will - do some tricks behind the scenes to get as many entities as possible. - Refer to `issue 573`__ for more on this. - - -Note that :tl:`GetParticipantsRequest` returns :tl:`ChannelParticipants`, -which may have more information you need (like the role of the -participants, total count of members, etc.) - -__ https://github.com/LonamiWebs/Telethon/issues/573 - - -Recent Actions -************** - -"Recent actions" is simply the name official applications have given to -the "admin log". Simply use :tl:`GetAdminLogRequest` for that, and -you'll get AdminLogResults.events in return which in turn has the final -`.action`__. - -__ https://lonamiwebs.github.io/Telethon/types/channel_admin_log_event_action.html - - -Admin Permissions -***************** - -Giving or revoking admin permissions can be done with the :tl:`EditAdminRequest`: - -.. code-block:: python - - from telethon.tl.functions.channels import EditAdminRequest - from telethon.tl.types import ChannelAdminRights - - # You need both the channel and who to grant permissions - # They can either be channel/user or input channel/input user. - # - # ChannelAdminRights is a list of granted permissions. - # Set to True those you want to give. - rights = ChannelAdminRights( - post_messages=None, - add_admins=None, - invite_users=None, - change_info=True, - ban_users=None, - delete_messages=True, - pin_messages=True, - invite_link=None, - edit_messages=None - ) - # Equivalent to: - # rights = ChannelAdminRights( - # change_info=True, - # delete_messages=True, - # pin_messages=True - # ) - - # Once you have a ChannelAdminRights, invoke it - client(EditAdminRequest(channel, user, rights)) - - # User will now be able to change group info, delete other people's - # messages and pin messages. - - -.. note:: - - Thanks to `@Kyle2142`__ for `pointing out`__ that you **cannot** set all - parameters to ``True`` to give a user full permissions, as not all - permissions are related to both broadcast channels/megagroups. - - E.g. trying to set ``post_messages=True`` in a megagroup will raise an - error. It is recommended to always use keyword arguments, and to set only - the permissions the user needs. If you don't need to change a permission, - it can be omitted (full list `here`__). - - -Restricting Users -***************** - -Similar to how you give or revoke admin permissions, you can edit the -banned rights of an user through :tl:`EditBannedRequest` and its parameter -:tl:`ChannelBannedRights`: - -.. code-block:: python - - from telethon.tl.functions.channels import EditBannedRequest - from telethon.tl.types import ChannelBannedRights - - from datetime import datetime, timedelta - - # Restricting an user for 7 days, only allowing view/send messages. - # - # Note that it's "reversed". You must set to ``True`` the permissions - # you want to REMOVE, and leave as ``None`` those you want to KEEP. - rights = ChannelBannedRights( - until_date=datetime.now() + timedelta(days=7), - view_messages=None, - send_messages=None, - send_media=True, - send_stickers=True, - send_gifs=True, - send_games=True, - send_inline=True, - embed_links=True - ) - - # The above is equivalent to - rights = ChannelBannedRights( - until_date=datetime.now() + timedelta(days=7), - send_media=True, - send_stickers=True, - send_gifs=True, - send_games=True, - send_inline=True, - embed_links=True - ) - - client(EditBannedRequest(channel, user, rights)) - - -Kicking a member -**************** - -Telegram doesn't actually have a request to kick an user from a group. -Instead, you need to restrict them so they can't see messages. Any date -is enough: - -.. code-block:: python - - from telethon.tl.functions.channels import EditBannedRequest - from telethon.tl.types import ChannelBannedRights - - client(EditBannedRequest( - channel, user, ChannelBannedRights( - until_date=None, - view_messages=True - ) - )) - - -__ https://github.com/Kyle2142 -__ https://github.com/LonamiWebs/Telethon/issues/490 -__ https://lonamiwebs.github.io/Telethon/constructors/channel_admin_rights.html - - -Increasing View Count in a Channel -********************************** - -It has been asked `quite`__ `a few`__ `times`__ (really, `many`__), and -while I don't understand why so many people ask this, the solution is to -use :tl:`GetMessagesViewsRequest`, setting ``increment=True``: - -.. code-block:: python - - - # Obtain `channel' through dialogs or through client.get_entity() or anyhow. - # Obtain `msg_ids' through `.get_messages()` or anyhow. Must be a list. - - client(GetMessagesViewsRequest( - peer=channel, - id=msg_ids, - increment=True - )) - - -Note that you can only do this **once or twice a day** per account, -running this in a loop will obviously not increase the views forever -unless you wait a day between each iteration. If you run it any sooner -than that, the views simply won't be increased. - -__ https://github.com/LonamiWebs/Telethon/issues/233 -__ https://github.com/LonamiWebs/Telethon/issues/305 -__ https://github.com/LonamiWebs/Telethon/issues/409 -__ https://github.com/LonamiWebs/Telethon/issues/447 diff --git a/readthedocs/extra/examples/projects-using-telethon.rst b/readthedocs/extra/examples/projects-using-telethon.rst deleted file mode 100644 index 3c92e660..00000000 --- a/readthedocs/extra/examples/projects-using-telethon.rst +++ /dev/null @@ -1,44 +0,0 @@ -======================= -Projects using Telethon -======================= - -This page lists some real world examples showcasing what can be built with -the library. - -.. note:: - - Do you have a project that uses the library or know of any that's not - listed here? Feel free to leave a comment at - `issue 744 `_ - so it can be included in the next revision of the documentation! - -.. _projects-telegram-export: - -telegram-export -*************** - -`Link `_ / -`Author's website `_ - -A tool to download Telegram data (users, chats, messages, and media) -into a database (and display the saved data). - -.. _projects-mautrix-telegram: - -mautrix-telegram -**************** - -`Link `_ / -`Author's website `_ - -A Matrix-Telegram hybrid puppeting/relaybot bridge. - -.. _projects-telegramtui: - -TelegramTUI -*********** - -`Link `_ / -`Author's website `_ - -A Telegram client on your terminal. diff --git a/readthedocs/extra/examples/users.rst b/readthedocs/extra/examples/users.rst deleted file mode 100644 index e029596e..00000000 --- a/readthedocs/extra/examples/users.rst +++ /dev/null @@ -1,72 +0,0 @@ -===== -Users -===== - - -.. note:: - - These examples assume you have read :ref:`accessing-the-full-api`. - - -Retrieving full information -*************************** - -If you need to retrieve the bio, biography or about information for an user -you should use :tl:`GetFullUser`: - - -.. code-block:: python - - from telethon.tl.functions.users import GetFullUserRequest - - full = client(GetFullUserRequest(user)) - # or even - full = client(GetFullUserRequest('username')) - - bio = full.about - - -See :tl:`UserFull` to know what other fields you can access. - - -Updating your name and/or bio -***************************** - -The first name, last name and bio (about) can all be changed with the same -request. Omitted fields won't change after invoking :tl:`UpdateProfile`: - -.. code-block:: python - - from telethon.tl.functions.account import UpdateProfileRequest - - client(UpdateProfileRequest(a - bout='This is a test from Telethon' - )) - - -Updating your username -********************** - -You need to use :tl:`account.UpdateUsername`: - -.. code-block:: python - - from telethon.tl.functions.account import UpdateUsernameRequest - - client(UpdateUsernameRequest('new_username')) - - -Updating your profile photo -*************************** - -The easiest way is to upload a new file and use that as the profile photo -through :tl:`UploadProfilePhoto`: - - -.. code-block:: python - - from telethon.tl.functions.photos import UploadProfilePhotoRequest - - client(UploadProfilePhotoRequest( - client.upload_file('/path/to/some/file') - ))) diff --git a/readthedocs/extra/examples/working-with-messages.rst b/readthedocs/extra/examples/working-with-messages.rst deleted file mode 100644 index 4f741425..00000000 --- a/readthedocs/extra/examples/working-with-messages.rst +++ /dev/null @@ -1,138 +0,0 @@ -===================== -Working with messages -===================== - - -.. note:: - - These examples assume you have read :ref:`accessing-the-full-api`. - - -Forwarding messages -******************* - -.. note:: - - Use the `telethon.client.messages.MessageMethods.forward_messages` - friendly method instead unless you have a better reason not to! - - This method automatically accepts either a single message or many of them. - -.. code-block:: python - - # If you only have the message IDs - client.forward_messages( - entity, # to which entity you are forwarding the messages - message_ids, # the IDs of the messages (or message) to forward - from_entity # who sent the messages? - ) - - # If you have ``Message`` objects - client.forward_messages( - entity, # to which entity you are forwarding the messages - messages # the messages (or message) to forward - ) - - # You can also do it manually if you prefer - from telethon.tl.functions.messages import ForwardMessagesRequest - - messages = foo() # retrieve a few messages (or even one, in a list) - from_entity = bar() - to_entity = baz() - - client(ForwardMessagesRequest( - from_peer=from_entity, # who sent these messages? - id=[msg.id for msg in messages], # which are the messages? - to_peer=to_entity # who are we forwarding them to? - )) - -The named arguments are there for clarity, although they're not needed because -they appear in order. You can obviously just wrap a single message on the list -too, if that's all you have. - - -Searching Messages -******************* - -.. note:: - - Use the `telethon.client.messages.MessageMethods.iter_messages` - friendly method instead unless you have a better reason not to! - - This method has ``search`` and ``filter`` parameters that will - suit your needs. - -Messages are searched through the obvious :tl:`SearchRequest`, but you may run -into issues_. A valid example would be: - -.. code-block:: python - - from telethon.tl.functions.messages import SearchRequest - from telethon.tl.types import InputMessagesFilterEmpty - - filter = InputMessagesFilterEmpty() - result = client(SearchRequest( - peer=peer, # On which chat/conversation - q='query', # What to search for - filter=filter, # Filter to use (maybe filter for media) - min_date=None, # Minimum date - max_date=None, # Maximum date - offset_id=0, # ID of the message to use as offset - add_offset=0, # Additional offset - limit=10, # How many results - max_id=0, # Maximum message ID - min_id=0, # Minimum message ID - from_id=None, # Who must have sent the message (peer) - hash=0 # Special number to return nothing on no-change - )) - -It's important to note that the optional parameter ``from_id`` could have -been omitted (defaulting to ``None``). Changing it to :tl:`InputUserEmpty`, as one -could think to specify "no user", won't work because this parameter is a flag, -and it being unspecified has a different meaning. - -If one were to set ``from_id=InputUserEmpty()``, it would filter messages -from "empty" senders, which would likely match no users. - -If you get a ``ChatAdminRequiredError`` on a channel, it's probably because -you tried setting the ``from_id`` filter, and as the error says, you can't -do that. Leave it set to ``None`` and it should work. - -As with every method, make sure you use the right ID/hash combination for -your :tl:`InputUser` or :tl:`InputChat`, or you'll likely run into errors like -``UserIdInvalidError``. - - -Sending stickers -**************** - -Stickers are nothing else than ``files``, and when you successfully retrieve -the stickers for a certain sticker set, all you will have are ``handles`` to -these files. Remember, the files Telegram holds on their servers can be -referenced through this pair of ID/hash (unique per user), and you need to -use this handle when sending a "document" message. This working example will -send yourself the very first sticker you have: - -.. code-block:: python - - # Get all the sticker sets this user has - from telethon.tl.functions.messages import GetAllStickersRequest - sticker_sets = client(GetAllStickersRequest(0)) - - # Choose a sticker set - from telethon.tl.functions.messages import GetStickerSetRequest - from telethon.tl.types import InputStickerSetID - sticker_set = sticker_sets.sets[0] - - # Get the stickers for this sticker set - stickers = client(GetStickerSetRequest( - stickerset=InputStickerSetID( - id=sticker_set.id, access_hash=sticker_set.access_hash - ) - )) - - # Stickers are nothing more than files, so send that - client.send_file('me', stickers.documents[0]) - - -.. _issues: https://github.com/LonamiWebs/Telethon/issues/215 diff --git a/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst b/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst deleted file mode 100644 index 671306c4..00000000 --- a/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst +++ /dev/null @@ -1,27 +0,0 @@ -======================================== -Deleted, Limited or Deactivated Accounts -======================================== - -If you're from Iran or Russia, we have bad news for you. Telegram is much more -likely to ban these numbers, as they are often used to spam other accounts, -likely through the use of libraries like this one. The best advice we can -give you is to not abuse the API, like calling many requests really quickly, -and to sign up with these phones through an official application. - -We have also had reports from Kazakhstan and China, where connecting -would fail. To solve these connection problems, you should use a proxy. - -Telegram may also ban virtual (VoIP) phone numbers, -as again, they're likely to be used for spam. - -If you want to check if your account has been limited, -simply send a private message to `@SpamBot`__ through Telegram itself. -You should notice this by getting errors like ``PeerFloodError``, -which means you're limited, for instance, -when sending a message to some accounts but not others. - -For more discussion, please see `issue 297`__. - - -__ https://t.me/SpamBot -__ https://github.com/LonamiWebs/Telethon/issues/297 diff --git a/readthedocs/extra/troubleshooting/enable-logging.rst b/readthedocs/extra/troubleshooting/enable-logging.rst deleted file mode 100644 index 8d8747f4..00000000 --- a/readthedocs/extra/troubleshooting/enable-logging.rst +++ /dev/null @@ -1,40 +0,0 @@ -================ -Enabling Logging -================ - -Telethon makes use of the `logging`__ module, and you can enable it as follows: - -.. code:: python - - import logging - logging.basicConfig(level=logging.DEBUG) - -The library has the `NullHandler`__ added by default so that no log calls -will be printed unless you explicitly enable it. - -You can also `use the module`__ on your own project very easily: - -.. code-block:: python - - import logging - logger = logging.getLogger(__name__) - - logger.debug('Debug messages') - logger.info('Useful information') - logger.warning('This is a warning!') - - -If you want to enable ``logging`` for your project *but* use a different -log level for the library: - -.. code-block:: python - - import logging - logging.basicConfig(level=logging.DEBUG) - # For instance, show only warnings and above - logging.getLogger('telethon').setLevel(level=logging.WARNING) - - -__ https://docs.python.org/3/library/logging.html -__ https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library -__ https://docs.python.org/3/howto/logging.html diff --git a/readthedocs/extra/troubleshooting/rpc-errors.rst b/readthedocs/extra/troubleshooting/rpc-errors.rst deleted file mode 100644 index 17299f1f..00000000 --- a/readthedocs/extra/troubleshooting/rpc-errors.rst +++ /dev/null @@ -1,29 +0,0 @@ -========== -RPC Errors -========== - -RPC stands for Remote Procedure Call, and when the library raises -a ``RPCError``, it's because you have invoked some of the API -methods incorrectly (wrong parameters, wrong permissions, or even -something went wrong on Telegram's server). All the errors are -available in :ref:`telethon-errors-package`, but some examples are: - -- ``FloodWaitError`` (420), the same request was repeated many times. - Must wait ``.seconds`` (you can access this parameter). -- ``SessionPasswordNeededError``, if you have setup two-steps - verification on Telegram. -- ``CdnFileTamperedError``, if the media you were trying to download - from a CDN has been altered. -- ``ChatAdminRequiredError``, you don't have permissions to perform - said operation on a chat or channel. Try avoiding filters, i.e. when - searching messages. - -The generic classes for different error codes are: - -- ``InvalidDCError`` (303), the request must be repeated on another DC. -- ``BadRequestError`` (400), the request contained errors. -- ``UnauthorizedError`` (401), the user is not authorized yet. -- ``ForbiddenError`` (403), privacy violation error. -- ``NotFoundError`` (404), make sure you're invoking ``Request``\ 's! - -If the error is not recognised, it will only be an ``RPCError``. diff --git a/readthedocs/extra/wall-of-shame.rst b/readthedocs/extra/wall-of-shame.rst deleted file mode 100644 index 83e96956..00000000 --- a/readthedocs/extra/wall-of-shame.rst +++ /dev/null @@ -1,63 +0,0 @@ -============= -Wall of Shame -============= - - -This project has an -`issues `__ section for -you to file **issues** whenever you encounter any when working with the -library. Said section is **not** for issues on *your* program but rather -issues with Telethon itself. - -If you have not made the effort to 1. read through the docs and 2. -`look for the method you need `__, -you will end up on the `Wall of -Shame `__, -i.e. all issues labeled -`"RTFM" `__: - - **rtfm** - Literally "Read The F--king Manual"; a term showing the - frustration of being bothered with questions so trivial that the asker - could have quickly figured out the answer on their own with minimal - effort, usually by reading readily-available documents. People who - say"RTFM!" might be considered rude, but the true rude ones are the - annoying people who take absolutely no self-responibility and expect to - have all the answers handed to them personally. - - *"Damn, that's the twelveth time that somebody posted this question - to the messageboard today! RTFM, already!"* - - *by Bill M. July 27, 2004* - -If you have indeed read the docs, and have tried looking for the method, -and yet you didn't find what you need, **that's fine**. Telegram's API -can have some obscure names at times, and for this reason, there is a -`"question" -label `__ -with questions that are okay to ask. Just state what you've tried so -that we know you've made an effort, or you'll go to the Wall of Shame. - -Of course, if the issue you're going to open is not even a question but -a real issue with the library (thankfully, most of the issues have been -that!), you won't end up here. Don't worry. - -Current winner --------------- - -The current winner is `issue -213 `__: - -**Issue:** - -.. figure:: https://user-images.githubusercontent.com/6297805/29822978-9a9a6ef0-8ccd-11e7-9ec5-934ea0f57681.jpg -:alt: Winner issue - -Winner issue - -**Answer:** - -.. figure:: https://user-images.githubusercontent.com/6297805/29822983-9d523402-8ccd-11e7-9fb1-5783740ee366.jpg -:alt: Winner issue answer - -Winner issue answer diff --git a/readthedocs/index.rst b/readthedocs/index.rst deleted file mode 100644 index 090b1c69..00000000 --- a/readthedocs/index.rst +++ /dev/null @@ -1,135 +0,0 @@ -.. Telethon documentation master file, created by - sphinx-quickstart on Fri Nov 17 15:36:11 2017. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -==================================== -Welcome to Telethon's documentation! -==================================== - - -Pure Python 3 Telegram client library. -Official Site `here `_. -Please follow the links on the index below to navigate from here, -or use the menu on the left. Remember to read the :ref:`changelog` -when you upgrade! - -.. important:: - If you're new here, you want to read :ref:`getting-started`. If you're - looking for the method reference, you should check :ref:`telethon-client`. - - The mentioned :ref:`telethon-client` is an important section and it - contains the friendly methods that **you should use** most of the time. - - -.. note:: - The library uses `asyncio `_ - under the hood, but you don't need to know anything about it unless you're - going to work with updates! If you're an user of Telethon pre-1.0 and you - aren't ready to convert your event handlers into ``async``, you can use - `a simpler version `_ - (select the "sync" version in ``readthedocs``' bottom left corner). - - If you used Telethon pre-1.0 but your scripts don't use updates or threads, - running ``import telethon.sync`` should make them Just Work. Otherwise, - we have :ref:`asyncio-magic` to teach you why ``asyncio`` is good and - how to use it. - - -What is this? -************* - -Telegram is a popular messaging application. This library is meant -to make it easy for you to write Python programs that can interact -with Telegram. Think of it as a wrapper that has already done the -heavy job for you, so you can focus on developing an application. - - -.. _installation-and-usage: - -.. toctree:: - :maxdepth: 2 - :caption: Installation and Simple Usage - - extra/basic/getting-started - extra/basic/installation - extra/basic/creating-a-client - extra/basic/telegram-client - extra/basic/entities - extra/basic/asyncio-magic - extra/basic/working-with-updates - - -.. _Advanced-usage: - -.. toctree:: - :maxdepth: 2 - :caption: Advanced Usage - - extra/advanced-usage/accessing-the-full-api - extra/advanced-usage/sessions - extra/advanced-usage/update-modes - - -.. _Examples: - -.. toctree:: - :maxdepth: 2 - :caption: Examples - - extra/examples/working-with-messages - extra/examples/chats-and-channels - extra/examples/users - extra/examples/bots - extra/examples/projects-using-telethon - - -.. _Troubleshooting: - -.. toctree:: - :maxdepth: 2 - :caption: Troubleshooting - - extra/troubleshooting/enable-logging - extra/troubleshooting/deleted-limited-or-deactivated-accounts - extra/troubleshooting/rpc-errors - - -.. _Developing: - -.. toctree:: - :maxdepth: 2 - :caption: Developing - - extra/developing/philosophy.rst - extra/developing/api-status.rst - extra/developing/test-servers.rst - extra/developing/project-structure.rst - extra/developing/coding-style.rst - extra/developing/understanding-the-type-language.rst - extra/developing/tips-for-porting-the-project.rst - extra/developing/telegram-api-in-other-languages.rst - - -.. _More: - -.. toctree:: - :maxdepth: 2 - :caption: More - - extra/changelog - extra/wall-of-shame.rst - - -.. toctree:: - :caption: Telethon modules - - modules - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/readthedocs/make.bat b/readthedocs/make.bat deleted file mode 100644 index f51f7234..00000000 --- a/readthedocs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=Telethon - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/readthedocs/modules.rst b/readthedocs/modules.rst deleted file mode 100644 index f710574a..00000000 --- a/readthedocs/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -telethon -======== - -.. toctree:: - :maxdepth: 3 - - telethon diff --git a/readthedocs/requirements.txt b/readthedocs/requirements.txt deleted file mode 100644 index 97c7493d..00000000 --- a/readthedocs/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -telethon \ No newline at end of file diff --git a/readthedocs/telethon.client.rst b/readthedocs/telethon.client.rst deleted file mode 100644 index b5378020..00000000 --- a/readthedocs/telethon.client.rst +++ /dev/null @@ -1,81 +0,0 @@ -.. _telethon-client: - - -telethon\.client package -======================== - -The `telethon.TelegramClient` aggregates several mixin classes to provide -all the common functionality in a nice, Pythonic interface. Each mixin has -its own methods, which you all can use. - -**In short, to create a client you must run:** - -.. code-block:: python - - import asyncio - from telethon import TelegramClient - - async def main(): - client = await TelegramClient(name, api_id, api_hash).start() - # Now you can use all client methods listed below, like for example... - await client.send_message('me', 'Hello to myself!') - - asyncio.get_event_loop().run_until_complete(main()) - - -You **don't** need to import these `AuthMethods`, `MessageMethods`, etc. -Together they are the `telethon.TelegramClient` and you can access all of -their methods. - - -.. automodule:: telethon.client.auth - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.client.chats - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.dialogs - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.messageparse - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.messages - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.updates - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.downloads - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.uploads - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: telethon.client.users - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.client.telegrambaseclient - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.errors.rst b/readthedocs/telethon.errors.rst deleted file mode 100644 index f69e2967..00000000 --- a/readthedocs/telethon.errors.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. _telethon-errors-package: - - -telethon\.errors package -======================== - - -telethon\.errors\.common module -------------------------------- - -.. automodule:: telethon.errors.common - :members: - :undoc-members: - :show-inheritance: - -telethon\.errors\.rpcbaseerrors module --------------------------------------- - -.. automodule:: telethon.errors.rpcbaseerrors - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.events.rst b/readthedocs/telethon.events.rst deleted file mode 100644 index 0d18415a..00000000 --- a/readthedocs/telethon.events.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. _telethon-events-package: - -telethon\.events package -======================== - -Every event (builder) subclasses `telethon.events.common.EventBuilder`, -so all the methods in it can be used from any event builder/event instance. - -.. automodule:: telethon.events.common - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.newmessage - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.chataction - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.userupdate - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.messageedited - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.messagedeleted - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.messageread - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events.raw - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: telethon.events - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.extensions.rst b/readthedocs/telethon.extensions.rst deleted file mode 100644 index 2b834277..00000000 --- a/readthedocs/telethon.extensions.rst +++ /dev/null @@ -1,35 +0,0 @@ -telethon\.extensions package -============================ - - -telethon\.extensions\.binaryreader module ------------------------------------------ - -.. automodule:: telethon.extensions.binaryreader - :members: - :undoc-members: - :show-inheritance: - -telethon\.extensions\.markdown module -------------------------------------- - -.. automodule:: telethon.extensions.markdown - :members: - :undoc-members: - :show-inheritance: - -telethon\.extensions\.html module ---------------------------------- - -.. automodule:: telethon.extensions.html - :members: - :undoc-members: - :show-inheritance: - -telethon\.extensions\.tcpclient module --------------------------------------- - -.. automodule:: telethon.extensions.tcpclient - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.network.rst b/readthedocs/telethon.network.rst deleted file mode 100644 index 79da891b..00000000 --- a/readthedocs/telethon.network.rst +++ /dev/null @@ -1,35 +0,0 @@ -telethon\.network package -========================= - - -telethon\.network\.connection module ------------------------------------- - -.. automodule:: telethon.network.connection - :members: - :undoc-members: - :show-inheritance: - -telethon\.network\.mtprotoplainsender module ------------------------------------------------- - -.. automodule:: telethon.network.mtprotoplainsender - :members: - :undoc-members: - :show-inheritance: - -telethon\.network\.mtprotosender module ------------------------------------------ - -.. automodule:: telethon.network.mtprotosender - :members: - :undoc-members: - :show-inheritance: - -telethon\.network\.authenticator module ---------------------------------------- - -.. automodule:: telethon.network.authenticator - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.rst b/readthedocs/telethon.rst deleted file mode 100644 index dadf0810..00000000 --- a/readthedocs/telethon.rst +++ /dev/null @@ -1,81 +0,0 @@ -.. _telethon-package: - - -telethon package -================ - - -telethon\.client module ------------------------ - -.. toctree:: - - telethon.client - -.. automodule:: telethon.client - :members: - :undoc-members: - :show-inheritance: - - -telethon\.utils module ----------------------- - -.. automodule:: telethon.utils - :members: - :undoc-members: - :show-inheritance: - - -telethon\.events package ------------------------- - -.. toctree:: - - telethon.events - - -telethon\.sessions module -------------------------- - -.. automodule:: telethon.sessions - :members: - :undoc-members: - :show-inheritance: - -telethon\.errors package ------------------------- - -.. toctree:: - - telethon.errors - -telethon\.extensions package ----------------------------- - -.. toctree:: - - telethon.extensions - -telethon\.network package -------------------------- - -.. toctree:: - - telethon.network - -telethon\.tl package --------------------- - -.. toctree:: - - telethon.tl - - -Module contents ---------------- - -.. automodule:: telethon - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.tl.custom.rst b/readthedocs/telethon.tl.custom.rst deleted file mode 100644 index e8b6c8e6..00000000 --- a/readthedocs/telethon.tl.custom.rst +++ /dev/null @@ -1,47 +0,0 @@ -telethon\.tl\.custom package -============================ - - -telethon\.tl\.custom\.draft module ----------------------------------- - -.. automodule:: telethon.tl.custom.draft - :members: - :undoc-members: - :show-inheritance: - - - -telethon\.tl\.custom\.dialog module ------------------------------------ - -.. automodule:: telethon.tl.custom.dialog - :members: - :undoc-members: - :show-inheritance: - - -telethon\.tl\.custom\.message module ------------------------------------- - -.. automodule:: telethon.tl.custom.message - :members: - :undoc-members: - :show-inheritance: - - -telethon\.tl\.custom\.messagebutton module ------------------------------------------- - -.. automodule:: telethon.tl.custom.messagebutton - :members: - :undoc-members: - :show-inheritance: - -telethon\.tl\.custom\.forward module ------------------------------------- - -.. automodule:: telethon.tl.custom.forward - :members: - :undoc-members: - :show-inheritance: diff --git a/readthedocs/telethon.tl.rst b/readthedocs/telethon.tl.rst deleted file mode 100644 index 7e8c1da1..00000000 --- a/readthedocs/telethon.tl.rst +++ /dev/null @@ -1,16 +0,0 @@ -telethon\.tl\.custom package -============================ - - -.. toctree:: - - telethon.tl.custom - - -telethon\.tl\.tlobject module ------------------------------ - -.. automodule:: telethon.tl.tlobject - :members: - :undoc-members: - :show-inheritance: diff --git a/setup.py b/setup.py index 43cb66e2..62287f7f 100755 --- a/setup.py +++ b/setup.py @@ -183,7 +183,7 @@ def main(): version = re.search(r"^__version__\s*=\s*'(.*)'.*$", f.read(), flags=re.MULTILINE).group(1) setup( - name='Telethon', + name='Telethon-sync', version=version, description="Full-featured Telegram client library for Python 3", long_description=long_description, @@ -199,7 +199,7 @@ def main(): # See https://stackoverflow.com/a/40300957/4759433 # -> https://www.python.org/dev/peps/pep-0345/#requires-python # -> http://setuptools.readthedocs.io/en/latest/setuptools.html - python_requires='>=3.5', + python_requires='>=3.4', # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ @@ -214,6 +214,7 @@ def main(): 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6' ], diff --git a/telethon/client/auth.py b/telethon/client/auth.py index 6f86a733..2a935c69 100644 --- a/telethon/client/auth.py +++ b/telethon/client/auth.py @@ -38,7 +38,7 @@ class AuthMethods(MessageParseMethods, UserMethods): (You are now logged in) If the event loop is already running, this method returns a - coroutine that you should await on your own code; otherwise + coroutine that you should on your own code; otherwise the loop is ran until said coroutine completes. Args: @@ -109,17 +109,17 @@ class AuthMethods(MessageParseMethods, UserMethods): else self.loop.run_until_complete(coro) ) - async def _start( + def _start( self, phone, password, bot_token, force_sms, code_callback, first_name, last_name, max_attempts): if not self.is_connected(): - await self.connect() + self.connect() - if await self.is_user_authorized(): + if self.is_user_authorized(): return self if bot_token: - await self.sign_in(bot_token=bot_token) + self.sign_in(bot_token=bot_token) return self # Turn the callable into a valid phone number @@ -130,16 +130,16 @@ class AuthMethods(MessageParseMethods, UserMethods): attempts = 0 two_step_detected = False - sent_code = await self.send_code_request(phone, force_sms=force_sms) + sent_code = self.send_code_request(phone, force_sms=force_sms) sign_up = not sent_code.phone_registered while attempts < max_attempts: try: if sign_up: - me = await self.sign_up( + me = self.sign_up( code_callback(), first_name, last_name) else: # Raises SessionPasswordNeededError if 2FA enabled - me = await self.sign_in(phone, code=code_callback()) + me = self.sign_in(phone, code=code_callback()) break except errors.SessionPasswordNeededError: two_step_detected = True @@ -171,7 +171,7 @@ class AuthMethods(MessageParseMethods, UserMethods): if callable(password): for _ in range(max_attempts): try: - me = await self.sign_in( + me = self.sign_in( phone=phone, password=password()) break except errors.PasswordHashInvalidError: @@ -180,7 +180,7 @@ class AuthMethods(MessageParseMethods, UserMethods): else: raise errors.PasswordHashInvalidError() else: - me = await self.sign_in(phone=phone, password=password) + me = self.sign_in(phone=phone, password=password) # We won't reach here if any step failed (exit by exception) signed, name = 'Signed in successfully as', utils.get_display_name(me) @@ -193,7 +193,7 @@ class AuthMethods(MessageParseMethods, UserMethods): return self - async def sign_in( + def sign_in( self, phone=None, *, code=None, password=None, bot_token=None, phone_code_hash=None): """ @@ -228,12 +228,12 @@ class AuthMethods(MessageParseMethods, UserMethods): The signed in user, or the information about :meth:`send_code_request`. """ - me = await self.get_me() + me = self.get_me() if me: return me if phone and not code and not password: - return await self.send_code_request(phone) + return self.send_code_request(phone) elif code: phone = utils.parse_phone(phone) or self._phone phone_code_hash = \ @@ -248,16 +248,16 @@ class AuthMethods(MessageParseMethods, UserMethods): # May raise PhoneCodeEmptyError, PhoneCodeExpiredError, # PhoneCodeHashEmptyError or PhoneCodeInvalidError. - result = await self(functions.auth.SignInRequest( + result = self(functions.auth.SignInRequest( phone, phone_code_hash, str(code))) elif password: - salt = (await self( + salt = (self( functions.account.GetPasswordRequest())).current_salt - result = await self(functions.auth.CheckPasswordRequest( + result = self(functions.auth.CheckPasswordRequest( helpers.get_password_hash(password, salt) )) elif bot_token: - result = await self(functions.auth.ImportBotAuthorizationRequest( + result = self(functions.auth.ImportBotAuthorizationRequest( flags=0, bot_auth_token=bot_token, api_id=self.api_id, api_hash=self.api_hash )) @@ -273,7 +273,7 @@ class AuthMethods(MessageParseMethods, UserMethods): self._authorized = True return result.user - async def sign_up(self, code, first_name, last_name=''): + def sign_up(self, code, first_name, last_name=''): """ Signs up to Telegram if you don't have an account yet. You must call .send_code_request(phone) first. @@ -296,7 +296,7 @@ class AuthMethods(MessageParseMethods, UserMethods): Returns: The new created :tl:`User`. """ - me = await self.get_me() + me = self.get_me() if me: return me @@ -308,7 +308,7 @@ class AuthMethods(MessageParseMethods, UserMethods): sys.stderr.write("{}\n".format(t)) sys.stderr.flush() - result = await self(functions.auth.SignUpRequest( + result = self(functions.auth.SignUpRequest( phone_number=self._phone, phone_code_hash=self._phone_code_hash.get(self._phone, ''), phone_code=str(code), @@ -317,7 +317,7 @@ class AuthMethods(MessageParseMethods, UserMethods): )) if self._tos: - await self( + self( functions.help.AcceptTermsOfServiceRequest(self._tos.id)) self._self_input_peer = utils.get_input_peer( @@ -326,7 +326,7 @@ class AuthMethods(MessageParseMethods, UserMethods): self._authorized = True return result.user - async def send_code_request(self, phone, *, force_sms=False): + def send_code_request(self, phone, *, force_sms=False): """ Sends a code request to the specified phone number. @@ -345,7 +345,7 @@ class AuthMethods(MessageParseMethods, UserMethods): if not phone_hash: try: - result = await self(functions.auth.SendCodeRequest( + result = self(functions.auth.SendCodeRequest( phone, self.api_id, self.api_hash)) except errors.AuthRestartError: return self.send_code_request(phone, force_sms=force_sms) @@ -358,14 +358,14 @@ class AuthMethods(MessageParseMethods, UserMethods): self._phone = phone if force_sms: - result = await self( + result = self( functions.auth.ResendCodeRequest(phone, phone_hash)) self._phone_code_hash[phone] = result.phone_code_hash return result - async def log_out(self): + def log_out(self): """ Logs out Telegram and deletes the current ``*.session`` file. @@ -373,16 +373,16 @@ class AuthMethods(MessageParseMethods, UserMethods): ``True`` if the operation was successful. """ try: - await self(functions.auth.LogOutRequest()) + self(functions.auth.LogOutRequest()) except errors.RPCError: return False - await self.disconnect() + self.disconnect() self.session.delete() self._authorized = False return True - async def edit_2fa( + def edit_2fa( self, current_password=None, new_password=None, *, hint='', email=None): """ @@ -418,7 +418,7 @@ class AuthMethods(MessageParseMethods, UserMethods): if new_password is None and current_password is None: return False - pass_result = await self(functions.account.GetPasswordRequest()) + pass_result = self(functions.account.GetPasswordRequest()) if isinstance( pass_result, types.account.NoPassword) and current_password: current_password = None @@ -445,11 +445,11 @@ class AuthMethods(MessageParseMethods, UserMethods): ) if email: # If enabling 2FA or changing email new_settings.email = email # TG counts empty string as None - return await self(functions.account.UpdatePasswordSettingsRequest( + return self(functions.account.UpdatePasswordSettingsRequest( current_password_hash, new_settings=new_settings )) else: # Removing existing password - return await self(functions.account.UpdatePasswordSettingsRequest( + return self(functions.account.UpdatePasswordSettingsRequest( current_password_hash, new_settings=types.account.PasswordInputSettings( new_salt=bytes(), @@ -465,13 +465,13 @@ class AuthMethods(MessageParseMethods, UserMethods): def __enter__(self): return self.start() - async def __aenter__(self): - return await self.start() + def __aenter__(self): + return self.start() def __exit__(self, *args): self.disconnect() - async def __aexit__(self, *args): - await self.disconnect() + def __aexit__(self, *args): + self.disconnect() # endregion diff --git a/telethon/client/chats.py b/telethon/client/chats.py index a80bc3f4..e3a35b0d 100644 --- a/telethon/client/chats.py +++ b/telethon/client/chats.py @@ -12,7 +12,7 @@ class ChatMethods(UserMethods): # region Public methods @async_generator - async def iter_participants( + def iter_participants( self, entity, limit=None, *, search='', filter=None, aggressive=False, _total=None): """ @@ -61,7 +61,7 @@ class ChatMethods(UserMethods): else: filter = filter() - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) if search and (filter or not isinstance(entity, types.InputPeerChannel)): # We need to 'search' ourselves unless we have a PeerChannel @@ -77,7 +77,7 @@ class ChatMethods(UserMethods): limit = float('inf') if limit is None else int(limit) if isinstance(entity, types.InputPeerChannel): if _total or (aggressive and not filter): - total = (await self(functions.channels.GetFullChannelRequest( + total = (self(functions.channels.GetFullChannelRequest( entity ))).full_chat.participants_count if _total: @@ -117,7 +117,7 @@ class ChatMethods(UserMethods): if requests[0].offset > limit: break - results = await self(requests) + results = self(requests) for i in reversed(range(len(requests))): participants = results[i] if not participants.users: @@ -133,12 +133,12 @@ class ChatMethods(UserMethods): seen.add(participant.user_id) user = users[participant.user_id] user.participant = participant - await yield_(user) + yield_(user) if len(seen) >= limit: return elif isinstance(entity, types.InputPeerChat): - full = await self( + full = self( functions.messages.GetFullChatRequest(entity.chat_id)) if not isinstance( full.full_chat.participants, types.ChatParticipants): @@ -161,17 +161,17 @@ class ChatMethods(UserMethods): else: user = users[participant.user_id] user.participant = participant - await yield_(user) + yield_(user) else: if _total: _total[0] = 1 if limit != 0: - user = await self.get_entity(entity) + user = self.get_entity(entity) if filter_entity(user): user.participant = None - await yield_(user) + yield_(user) - async def get_participants(self, *args, **kwargs): + def get_participants(self, *args, **kwargs): """ Same as :meth:`iter_participants`, but returns a list instead with an additional ``.total`` attribute on the list. @@ -179,7 +179,7 @@ class ChatMethods(UserMethods): total = [0] kwargs['_total'] = total participants = UserList() - async for x in self.iter_participants(*args, **kwargs): + for x in self.iter_participants(*args, **kwargs): participants.append(x) participants.total = total[0] return participants diff --git a/telethon/client/dialogs.py b/telethon/client/dialogs.py index fa388732..32c88fc2 100644 --- a/telethon/client/dialogs.py +++ b/telethon/client/dialogs.py @@ -13,7 +13,7 @@ class DialogMethods(UserMethods): # region Public methods @async_generator - async def iter_dialogs( + def iter_dialogs( self, limit=None, *, offset_date=None, offset_id=0, offset_peer=types.InputPeerEmpty(), ignore_migrated=False, _total=None): @@ -56,7 +56,7 @@ class DialogMethods(UserMethods): if not _total: return # Special case, get a single dialog and determine count - dialogs = await self(functions.messages.GetDialogsRequest( + dialogs = self(functions.messages.GetDialogsRequest( offset_date=offset_date, offset_id=offset_id, offset_peer=offset_peer, @@ -74,7 +74,7 @@ class DialogMethods(UserMethods): ) while len(seen) < limit: req.limit = min(limit - len(seen), 100) - r = await self(req) + r = self(req) if _total: _total[0] = getattr(r, 'count', len(r.dialogs)) @@ -97,7 +97,7 @@ class DialogMethods(UserMethods): if not ignore_migrated or getattr( cd.entity, 'migrated_to', None) is None: - await yield_(cd) + yield_(cd) if len(r.dialogs) < req.limit\ or not isinstance(r, types.messages.DialogsSlice): @@ -116,7 +116,7 @@ class DialogMethods(UserMethods): req.offset_id = r.messages[-1].id req.exclude_pinned = True - async def get_dialogs(self, *args, **kwargs): + def get_dialogs(self, *args, **kwargs): """ Same as :meth:`iter_dialogs`, but returns a list instead with an additional ``.total`` attribute on the list. @@ -124,13 +124,13 @@ class DialogMethods(UserMethods): total = [0] kwargs['_total'] = total dialogs = UserList() - async for x in self.iter_dialogs(*args, **kwargs): + for x in self.iter_dialogs(*args, **kwargs): dialogs.append(x) dialogs.total = total[0] return dialogs @async_generator - async def iter_drafts(self): + def iter_drafts(self): """ Iterator over all open draft messages. @@ -139,16 +139,16 @@ class DialogMethods(UserMethods): to change the message or `telethon.tl.custom.draft.Draft.delete` among other things. """ - r = await self(functions.messages.GetAllDraftsRequest()) + r = self(functions.messages.GetAllDraftsRequest()) for update in r.updates: - await yield_(custom.Draft._from_update(self, update)) + yield_(custom.Draft._from_update(self, update)) - async def get_drafts(self): + def get_drafts(self): """ Same as :meth:`iter_drafts`, but returns a list instead. """ result = [] - async for x in self.iter_drafts(): + for x in self.iter_drafts(): result.append(x) return result diff --git a/telethon/client/downloads.py b/telethon/client/downloads.py index 318692b9..b52e2fa5 100644 --- a/telethon/client/downloads.py +++ b/telethon/client/downloads.py @@ -15,7 +15,7 @@ class DownloadMethods(UserMethods): # region Public methods - async def download_profile_photo( + def download_profile_photo( self, entity, file=None, *, download_big=True): """ Downloads the profile photo of the given entity (user/chat/channel). @@ -41,7 +41,7 @@ class DownloadMethods(UserMethods): # ('InputPeer', 'InputUser', 'InputChannel') INPUTS = (0xc91c90b6, 0xe669bf46, 0x40f202fd) if not isinstance(entity, TLObject) or entity.SUBCLASS_OF_ID in INPUTS: - entity = await self.get_entity(entity) + entity = self.get_entity(entity) possible_names = [] if entity.SUBCLASS_OF_ID not in ENTITIES: @@ -53,7 +53,7 @@ class DownloadMethods(UserMethods): if not hasattr(entity, 'chat_photo'): return None - return await self._download_photo( + return self._download_photo( entity.chat_photo, file, date=None, progress_callback=None) for attr in ('username', 'first_name', 'title'): @@ -75,15 +75,15 @@ class DownloadMethods(UserMethods): ) try: - await self.download_file(loc, file) + self.download_file(loc, file) return file except errors.LocationInvalidError: # See issue #500, Android app fails as of v4.6.0 (1155). # The fix seems to be using the full channel chat photo. - ie = await self.get_input_entity(entity) + ie = self.get_input_entity(entity) if isinstance(ie, types.InputPeerChannel): - full = await self(functions.channels.GetFullChannelRequest(ie)) - return await self._download_photo( + full = self(functions.channels.GetFullChannelRequest(ie)) + return self._download_photo( full.full_chat.chat_photo, file, date=None, progress_callback=None ) @@ -91,7 +91,7 @@ class DownloadMethods(UserMethods): # Until there's a report for chats, no need to. return None - async def download_media(self, message, file=None, + def download_media(self, message, file=None, *, progress_callback=None): """ Downloads the given media, or the media from a specified Message. @@ -129,11 +129,11 @@ class DownloadMethods(UserMethods): if isinstance(media, (types.MessageMediaPhoto, types.Photo, types.PhotoSize, types.PhotoCachedSize)): - return await self._download_photo( + return self._download_photo( media, file, date, progress_callback ) elif isinstance(media, (types.MessageMediaDocument, types.Document)): - return await self._download_document( + return self._download_document( media, file, date, progress_callback ) elif isinstance(media, types.MessageMediaContact): @@ -141,7 +141,7 @@ class DownloadMethods(UserMethods): media, file ) - async def download_file( + def download_file( self, input_location, file=None, *, part_size_kb=None, file_size=None, progress_callback=None): """ @@ -209,7 +209,7 @@ class DownloadMethods(UserMethods): offset = 0 while True: try: - result = await sender.send(functions.upload.GetFileRequest( + result = sender.send(functions.upload.GetFileRequest( input_location, offset, part_size )) if isinstance(result, types.upload.FileCdnRedirect): @@ -217,7 +217,7 @@ class DownloadMethods(UserMethods): raise NotImplementedError except errors.FileMigrateError as e: __log__.info('File lives in another DC') - sender = await self._get_exported_sender(e.new_dc) + sender = self._get_exported_sender(e.new_dc) continue offset += part_size @@ -234,7 +234,7 @@ class DownloadMethods(UserMethods): progress_callback(f.tell(), file_size) finally: if sender != self._sender: - await sender.disconnect() + sender.disconnect() if isinstance(file, str) or in_memory: f.close() @@ -242,7 +242,7 @@ class DownloadMethods(UserMethods): # region Private methods - async def _download_photo(self, photo, file, date, progress_callback): + def _download_photo(self, photo, file, date, progress_callback): """Specialized version of .download_media() for photos""" # Determine the photo and its largest size if isinstance(photo, types.MessageMediaPhoto): @@ -272,12 +272,12 @@ class DownloadMethods(UserMethods): f.close() return file - await self.download_file( + self.download_file( photo.location, file, file_size=photo.size, progress_callback=progress_callback) return file - async def _download_document( + def _download_document( self, document, file, date, progress_callback): """Specialized version of .download_media() for documents.""" if isinstance(document, types.MessageMediaDocument): @@ -311,7 +311,7 @@ class DownloadMethods(UserMethods): date=date, possible_names=possible_names ) - await self.download_file( + self.download_file( document, file, file_size=file_size, progress_callback=progress_callback) return file diff --git a/telethon/client/messageparse.py b/telethon/client/messageparse.py index 9c75f480..5180d565 100644 --- a/telethon/client/messageparse.py +++ b/telethon/client/messageparse.py @@ -45,7 +45,7 @@ class MessageParseMethods(UserMethods): # region Private methods - async def _replace_with_mention(self, entities, i, user): + def _replace_with_mention(self, entities, i, user): """ Helper method to replace ``entities[i]`` to mention ``user``, or do nothing if it can't be found. @@ -53,12 +53,12 @@ class MessageParseMethods(UserMethods): try: entities[i] = types.InputMessageEntityMentionName( entities[i].offset, entities[i].length, - await self.get_input_entity(user) + self.get_input_entity(user) ) except (ValueError, TypeError): pass - async def _parse_message_text(self, message, parse_mode): + def _parse_message_text(self, message, parse_mode): """ Returns a (parsed message, entities) tuple depending on ``parse_mode``. """ @@ -76,10 +76,10 @@ class MessageParseMethods(UserMethods): m = re.match(r'^@|\+|tg://user\?id=(\d+)', e.url) if m: user = int(m.group(1)) if m.group(1) else e.url - await self._replace_with_mention(msg_entities, i, user) + self._replace_with_mention(msg_entities, i, user) elif isinstance(e, (types.MessageEntityMentionName, types.InputMessageEntityMentionName)): - await self._replace_with_mention(msg_entities, i, e.user_id) + self._replace_with_mention(msg_entities, i, e.user_id) return message, msg_entities diff --git a/telethon/client/messages.py b/telethon/client/messages.py index 1c84be36..1d552277 100644 --- a/telethon/client/messages.py +++ b/telethon/client/messages.py @@ -21,7 +21,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # region Message retrieval @async_generator - async def iter_messages( + def iter_messages( self, entity, limit=None, *, offset_date=None, offset_id=0, max_id=0, min_id=0, add_offset=0, search=None, filter=None, from_user=None, batch_size=100, wait_time=None, ids=None, @@ -110,13 +110,13 @@ class MessageMethods(UploadMethods, MessageParseMethods): # It's possible to get messages by ID without their entity, so only # fetch the input version if we're not using IDs or if it was given. if not ids or entity: - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) if ids: if not utils.is_list_like(ids): ids = (ids,) - async for x in self._iter_ids(entity, ids, total=_total): - await yield_(x) + for x in self._iter_ids(entity, ids, total=_total): + yield_(x) return # Telegram doesn't like min_id/max_id. If these IDs are low enough @@ -147,7 +147,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): min_id=0, hash=0, from_id=( - await self.get_input_entity(from_user) + self.get_input_entity(from_user) if from_user else None ) ) @@ -155,7 +155,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # Telegram completely ignores `from_id` in private # chats, so we need to do this check client-side. if isinstance(request.from_id, types.InputPeerSelf): - from_id = (await self.get_me(input_peer=True)).user_id + from_id = (self.get_me(input_peer=True)).user_id else: from_id = request.from_id else: @@ -174,7 +174,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): if not _total: return # No messages, but we still need to know the total message count - result = await self(request) + result = self(request) if isinstance(result, types.messages.MessagesNotModified): _total[0] = result.count else: @@ -191,7 +191,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): start = asyncio.get_event_loop().time() # Telegram has a hard limit of 100 request.limit = min(limit - have, batch_size) - r = await self(request) + r = self(request) if _total: _total[0] = getattr(r, 'count', len(r.messages)) @@ -213,7 +213,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # IDs are returned in descending order. last_id = message.id - await yield_(custom.Message(self, message, entities, entity)) + yield_(custom.Message(self, message, entities, entity)) have += 1 if len(r.messages) < request.limit: @@ -243,10 +243,10 @@ class MessageMethods(UploadMethods, MessageParseMethods): request.max_date = last_message.date now = asyncio.get_event_loop().time() - await asyncio.sleep( + asyncio.sleep( max(wait_time - (now - start), 0), loop=self._loop) - async def get_messages(self, *args, **kwargs): + def get_messages(self, *args, **kwargs): """ Same as :meth:`iter_messages`, but returns a list instead with an additional ``.total`` attribute on the list. @@ -272,7 +272,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): kwargs['limit'] = 1 msgs = UserList() - async for x in self.iter_messages(*args, **kwargs): + for x in self.iter_messages(*args, **kwargs): msgs.append(x) msgs.total = total[0] if 'ids' in kwargs and not utils.is_list_like(kwargs['ids']): @@ -284,7 +284,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # region Message sending/editing/deleting - async def send_message( + def send_message( self, entity, message='', *, reply_to=None, parse_mode=utils.Default, link_preview=True, file=None, force_document=False, clear_draft=False): @@ -340,7 +340,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): The sent `telethon.tl.custom.message.Message`. """ if file is not None: - return await self.send_file( + return self.send_file( entity, file, caption=message, reply_to=reply_to, parse_mode=parse_mode, force_document=force_document ) @@ -349,11 +349,11 @@ class MessageMethods(UploadMethods, MessageParseMethods): 'The message cannot be empty unless a file is provided' ) - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) if isinstance(message, types.Message): if (message.media and not isinstance( message.media, types.MessageMediaWebPage)): - return await self.send_file( + return self.send_file( entity, message.media, caption=message.message, entities=message.entities ) @@ -377,7 +377,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): ) message = message.message else: - message, msg_ent = await self._parse_message_text(message, + message, msg_ent = self._parse_message_text(message, parse_mode) request = functions.messages.SendMessageRequest( peer=entity, @@ -388,7 +388,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): clear_draft=clear_draft ) - result = await self(request) + result = self(request) if isinstance(result, types.UpdateShortSentMessage): to_id, cls = utils.resolve_id(utils.get_peer_id(entity)) return custom.Message(self, types.Message( @@ -403,7 +403,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): return self._get_response_message(request, result, entity) - async def forward_messages(self, entity, messages, *, from_peer=None): + def forward_messages(self, entity, messages, *, from_peer=None): """ Forwards the given message(s) to the specified entity. @@ -448,7 +448,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): id=[m if isinstance(m, int) else m.id for m in messages], to_peer=entity ) - result = await self(req) + result = self(req) if isinstance(result, (types.Updates, types.UpdatesCombined)): entities = {utils.get_peer_id(x): x for x in itertools.chain(result.users, result.chats)} @@ -468,7 +468,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): result = [id_to_message[random_to_id[rnd]] for rnd in req.random_id] return result[0] if single else result - async def edit_message( + def edit_message( self, entity, message=None, text=None, *, parse_mode=utils.Default, link_preview=True, file=None): """ @@ -527,9 +527,9 @@ class MessageMethods(UploadMethods, MessageParseMethods): message = entity entity = entity.to_id - entity = await self.get_input_entity(entity) - text, msg_entities = await self._parse_message_text(text, parse_mode) - file_handle, media = await self._file_to_media(file) + entity = self.get_input_entity(entity) + text, msg_entities = self._parse_message_text(text, parse_mode) + file_handle, media = self._file_to_media(file) request = functions.messages.EditMessageRequest( peer=entity, id=utils.get_message_id(message), @@ -538,11 +538,11 @@ class MessageMethods(UploadMethods, MessageParseMethods): entities=msg_entities, media=media ) - msg = self._get_response_message(request, await self(request), entity) + msg = self._get_response_message(request, self(request), entity) self._cache_media(msg, file, file_handle) return msg - async def delete_messages(self, entity, message_ids, *, revoke=True): + def delete_messages(self, entity, message_ids, *, revoke=True): """ Deletes a message from a chat, optionally "for everyone". @@ -574,19 +574,19 @@ class MessageMethods(UploadMethods, MessageParseMethods): else int(m) for m in message_ids ) - entity = await self.get_input_entity(entity) if entity else None + entity = self.get_input_entity(entity) if entity else None if isinstance(entity, types.InputPeerChannel): - return await self([functions.channels.DeleteMessagesRequest( + return self([functions.channels.DeleteMessagesRequest( entity, list(c)) for c in utils.chunks(message_ids)]) else: - return await self([functions.messages.DeleteMessagesRequest( + return self([functions.messages.DeleteMessagesRequest( list(c), revoke) for c in utils.chunks(message_ids)]) # endregion # region Miscellaneous - async def send_read_acknowledge( + def send_read_acknowledge( self, entity, message=None, *, max_id=None, clear_mentions=False): """ Sends a "read acknowledge" (i.e., notifying the given peer that we've @@ -623,18 +623,18 @@ class MessageMethods(UploadMethods, MessageParseMethods): raise ValueError( 'Either a message list or a max_id must be provided.') - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) if clear_mentions: - await self(functions.messages.ReadMentionsRequest(entity)) + self(functions.messages.ReadMentionsRequest(entity)) if max_id is None: return True if max_id is not None: if isinstance(entity, types.InputPeerChannel): - return await self(functions.channels.ReadHistoryRequest( + return self(functions.channels.ReadHistoryRequest( entity, max_id=max_id)) else: - return await self(functions.messages.ReadHistoryRequest( + return self(functions.messages.ReadHistoryRequest( entity, max_id=max_id)) return False @@ -646,7 +646,7 @@ class MessageMethods(UploadMethods, MessageParseMethods): # region Private methods @async_generator - async def _iter_ids(self, entity, ids, total): + def _iter_ids(self, entity, ids, total): """ Special case for `iter_messages` when it should only fetch some IDs. """ @@ -655,15 +655,15 @@ class MessageMethods(UploadMethods, MessageParseMethods): from_id = None # By default, no need to validate from_id if isinstance(entity, types.InputPeerChannel): - r = await self(functions.channels.GetMessagesRequest(entity, ids)) + r = self(functions.channels.GetMessagesRequest(entity, ids)) else: - r = await self(functions.messages.GetMessagesRequest(ids)) + r = self(functions.messages.GetMessagesRequest(ids)) if entity: from_id = utils.get_peer_id(entity) if isinstance(r, types.messages.MessagesNotModified): for _ in ids: - await yield_(None) + yield_(None) return entities = {utils.get_peer_id(x): x @@ -678,8 +678,8 @@ class MessageMethods(UploadMethods, MessageParseMethods): for message in r.messages: if isinstance(message, types.MessageEmpty) or ( from_id and utils.get_peer_id(message.to_id) != from_id): - await yield_(None) + yield_(None) else: - await yield_(custom.Message(self, message, entities, entity)) + yield_(custom.Message(self, message, entities, entity)) # endregion diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index d0608814..bfc04d87 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -269,14 +269,14 @@ class TelegramBaseClient(abc.ABC): # region Connecting - async def connect(self): + def connect(self): """ Connects to Telegram. """ - await self._sender.connect( + self._sender.connect( self.session.server_address, self.session.port) - await self._sender.send(self._init_with( + self._sender.send(self._init_with( functions.help.GetConfigRequest())) self._updates_handle = self._loop.create_task(self._update_loop()) @@ -287,15 +287,15 @@ class TelegramBaseClient(abc.ABC): """ return self._sender.is_connected() - async def disconnect(self): + def disconnect(self): """ Disconnects from Telegram. """ - await self._disconnect() + self._disconnect() if getattr(self, 'session', None): self.session.close() - async def _disconnect(self): + def _disconnect(self): """ Disconnect only, without closing the session. Used in reconnections to different data centers, where we don't want to close the session @@ -305,9 +305,9 @@ class TelegramBaseClient(abc.ABC): # All properties may be ``None`` if `__init__` fails, and this # method will be called from `__del__` which would crash then. if getattr(self, '_sender', None): - await self._sender.disconnect() + self._sender.disconnect() if getattr(self, '_updates_handle', None): - await self._updates_handle + self._updates_handle def __del__(self): if not self.is_connected() or self.loop.is_closed(): @@ -324,20 +324,20 @@ class TelegramBaseClient(abc.ABC): else: self._loop.run_until_complete(self.disconnect()) - async def _switch_dc(self, new_dc): + def _switch_dc(self, new_dc): """ Permanently switches the current connection to the new data center. """ __log__.info('Reconnecting to new data center %s', new_dc) - dc = await self._get_dc(new_dc) + dc = self._get_dc(new_dc) self.session.set_dc(dc.id, dc.ip_address, dc.port) # auth_key's are associated with a server, which has now changed # so it's not valid anymore. Set to None to force recreating it. self.session.auth_key = self._sender.state.auth_key = None self.session.save() - await self._disconnect() - return await self.connect() + self._disconnect() + return self.connect() def _auth_key_callback(self, auth_key): """ @@ -352,14 +352,14 @@ class TelegramBaseClient(abc.ABC): # region Working with different connections/Data Centers - async def _get_dc(self, dc_id, cdn=False): + def _get_dc(self, dc_id, cdn=False): """Gets the Data Center (DC) associated to 'dc_id'""" cls = self.__class__ if not cls._config: - cls._config = await self(functions.help.GetConfigRequest()) + cls._config = self(functions.help.GetConfigRequest()) if cdn and not self._cdn_config: - cls._cdn_config = await self(functions.help.GetCdnConfigRequest()) + cls._cdn_config = self(functions.help.GetCdnConfigRequest()) for pk in cls._cdn_config.public_keys: rsa.add_key(pk.public_key) @@ -369,7 +369,7 @@ class TelegramBaseClient(abc.ABC): and bool(dc.ipv6) == self._use_ipv6 and bool(dc.cdn) == cdn ) - async def _get_exported_sender(self, dc_id): + def _get_exported_sender(self, dc_id): """ Returns a cached `MTProtoSender` for the given `dc_id`, or creates a new one if it doesn't exist yet, and imports a freshly exported @@ -378,32 +378,32 @@ class TelegramBaseClient(abc.ABC): # Thanks badoualy/kotlogram on /telegram/api/DefaultTelegramClient.kt # for clearly showing how to export the authorization auth = self._exported_auths.get(dc_id) - dc = await self._get_dc(dc_id) + dc = self._get_dc(dc_id) state = MTProtoState(auth) # Can't reuse self._sender._connection as it has its own seqno. # # If one were to do that, Telegram would reset the connection # with no further clues. sender = MTProtoSender(state, self._connection.clone(), self._loop) - await sender.connect(dc.ip_address, dc.port) + sender.connect(dc.ip_address, dc.port) if not auth: __log__.info('Exporting authorization for data center %s', dc) - auth = await self(functions.auth.ExportAuthorizationRequest(dc_id)) + auth = self(functions.auth.ExportAuthorizationRequest(dc_id)) req = self._init_with(functions.auth.ImportAuthorizationRequest( id=auth.id, bytes=auth.bytes )) - await sender.send(req) + sender.send(req) self._exported_auths[dc_id] = sender.state.auth_key return sender - async def _get_cdn_client(self, cdn_redirect): + def _get_cdn_client(self, cdn_redirect): """Similar to ._get_exported_client, but for CDNs""" # TODO Implement raise NotImplementedError session = self._exported_sessions.get(cdn_redirect.dc_id) if not session: - dc = await self._get_dc(cdn_redirect.dc_id, cdn=True) + dc = self._get_dc(cdn_redirect.dc_id, cdn=True) session = self.session.clone() session.set_dc(dc.id, dc.ip_address, dc.port) self._exported_sessions[cdn_redirect.dc_id] = session @@ -458,7 +458,7 @@ class TelegramBaseClient(abc.ABC): raise NotImplementedError @abc.abstractmethod - async def _handle_auto_reconnect(self): + def _handle_auto_reconnect(self): raise NotImplementedError # endregion diff --git a/telethon/client/updates.py b/telethon/client/updates.py index fe47e87d..78eeee8a 100644 --- a/telethon/client/updates.py +++ b/telethon/client/updates.py @@ -16,11 +16,11 @@ class UpdateMethods(UserMethods): # region Public methods - async def _run_until_disconnected(self): + def _run_until_disconnected(self): try: - await self.disconnected + self.disconnected except KeyboardInterrupt: - await self.disconnect() + self.disconnect() def run_until_disconnected(self): """ @@ -30,7 +30,7 @@ class UpdateMethods(UserMethods): to ``except`` it on your own code. If the loop is already running, this method returns a coroutine - that you should await on your own code. + that you should on your own code. """ if self.loop.is_running(): return self._run_until_disconnected() @@ -52,7 +52,7 @@ class UpdateMethods(UserMethods): >>> client = TelegramClient(...) >>> >>> @client.on(events.NewMessage) - ... async def handler(event): + ... def handler(event): ... ... ... >>> @@ -120,7 +120,7 @@ class UpdateMethods(UserMethods): """ return [(callback, event) for event, callback in self._event_builders] - async def catch_up(self): + def catch_up(self): state = self.session.get_update_state(0) if not state or not state.pts: return @@ -128,7 +128,7 @@ class UpdateMethods(UserMethods): self.session.catching_up = True try: while True: - d = await self(functions.updates.GetDifferenceRequest( + d = self(functions.updates.GetDifferenceRequest( state.pts, state.date, state.qts)) if isinstance(d, types.updates.DifferenceEmpty): state.date = d.date @@ -193,12 +193,12 @@ class UpdateMethods(UserMethods): # TODO make use of need_diff - async def _update_loop(self): + def _update_loop(self): # Pings' ID don't really need to be secure, just "random" rnd = lambda: random.randrange(-2**63, 2**63) while self.is_connected(): try: - await asyncio.wait_for( + asyncio.wait_for( self.disconnected, timeout=60, loop=self._loop ) continue # We actually just want to act upon timeout @@ -223,22 +223,22 @@ class UpdateMethods(UserMethods): # # TODO Call getDifference instead since it's more relevant if time.time() - self._last_request > 30 * 60: - if not await self.is_user_authorized(): + if not self.is_user_authorized(): # What can be the user doing for so # long without being logged in...? continue - await self(functions.updates.GetStateRequest()) + self(functions.updates.GetStateRequest()) - async def _dispatch_update(self, update): + def _dispatch_update(self, update): if self._events_pending_resolve: if self._event_resolve_lock.locked(): - async with self._event_resolve_lock: + with self._event_resolve_lock: pass else: - async with self._event_resolve_lock: + with self._event_resolve_lock: for event in self._events_pending_resolve: - await event.resolve(self) + event.resolve(self) self._events_pending_resolve.clear() @@ -252,7 +252,7 @@ class UpdateMethods(UserMethods): event.original_update = update try: - await callback(event) + callback(event) except events.StopPropagation: __log__.debug( "Event handler '{}' stopped chain of " @@ -265,12 +265,12 @@ class UpdateMethods(UserMethods): __log__.exception('Unhandled exception on {}' .format(callback.__name__)) - async def _handle_auto_reconnect(self): + def _handle_auto_reconnect(self): # Upon reconnection, we want to send getState # for Telegram to keep sending us updates. try: __log__.info('Asking for the current state after reconnect...') - state = await self(functions.updates.GetStateRequest()) + state = self(functions.updates.GetStateRequest()) __log__.info('Got new state! %s', state) except errors.RPCError as e: __log__.info('Failed to get current state: %r', e) diff --git a/telethon/client/uploads.py b/telethon/client/uploads.py index cd38d024..86745ec7 100644 --- a/telethon/client/uploads.py +++ b/telethon/client/uploads.py @@ -26,7 +26,7 @@ class UploadMethods(MessageParseMethods, UserMethods): # region Public methods - async def send_file( + def send_file( self, entity, file, *, caption='', force_document=False, progress_callback=None, reply_to=None, attributes=None, thumb=None, allow_cache=True, parse_mode=utils.Default, @@ -122,7 +122,7 @@ class UploadMethods(MessageParseMethods, UserMethods): result = [] while images: - result += await self._send_album( + result += self._send_album( entity, images[:10], caption=caption, progress_callback=progress_callback, reply_to=reply_to, parse_mode=parse_mode @@ -130,7 +130,7 @@ class UploadMethods(MessageParseMethods, UserMethods): images = images[10:] for x in documents: - result.append(await self.send_file( + result.append(self.send_file( entity, x, allow_cache=allow_cache, caption=caption, force_document=force_document, progress_callback=progress_callback, reply_to=reply_to, @@ -140,7 +140,7 @@ class UploadMethods(MessageParseMethods, UserMethods): return result - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) reply_to = utils.get_message_id(reply_to) # Not document since it's subject to change. @@ -149,9 +149,9 @@ class UploadMethods(MessageParseMethods, UserMethods): msg_entities = kwargs['entities'] else: caption, msg_entities =\ - await self._parse_message_text(caption, parse_mode) + self._parse_message_text(caption, parse_mode) - file_handle, media = await self._file_to_media( + file_handle, media = self._file_to_media( file, force_document=force_document, progress_callback=progress_callback, attributes=attributes, allow_cache=allow_cache, thumb=thumb, @@ -162,12 +162,12 @@ class UploadMethods(MessageParseMethods, UserMethods): entity, media, reply_to_msg_id=reply_to, message=caption, entities=msg_entities ) - msg = self._get_response_message(request, await self(request), entity) + msg = self._get_response_message(request, self(request), entity) self._cache_media(msg, file, file_handle, force_document=force_document) return msg - async def _send_album(self, entity, files, caption='', + def _send_album(self, entity, files, caption='', progress_callback=None, reply_to=None, parse_mode=utils.Default): """Specialized version of .send_file for albums""" @@ -180,13 +180,13 @@ class UploadMethods(MessageParseMethods, UserMethods): # In theory documents can be sent inside the albums but they appear # as different messages (not inside the album), and the logic to set # the attributes/avoid cache is already written in .send_file(). - entity = await self.get_input_entity(entity) + entity = self.get_input_entity(entity) if not utils.is_list_like(caption): caption = (caption,) captions = [] for c in reversed(caption): # Pop from the end (so reverse) - captions.append(await self._parse_message_text(c or '', parse_mode)) + captions.append(self._parse_message_text(c or '', parse_mode)) reply_to = utils.get_message_id(reply_to) @@ -194,9 +194,9 @@ class UploadMethods(MessageParseMethods, UserMethods): media = [] for file in files: # fh will either be InputPhoto or a modified InputFile - fh = await self.upload_file(file, use_cache=types.InputPhoto) + fh = self.upload_file(file, use_cache=types.InputPhoto) if not isinstance(fh, types.InputPhoto): - r = await self(functions.messages.UploadMediaRequest( + r = self(functions.messages.UploadMediaRequest( entity, media=types.InputMediaUploadedPhoto(fh) )) input_photo = utils.get_input_photo(r.photo) @@ -211,7 +211,7 @@ class UploadMethods(MessageParseMethods, UserMethods): entities=msg_entities)) # Now we can construct the multi-media request - result = await self(functions.messages.SendMultiMediaRequest( + result = self(functions.messages.SendMultiMediaRequest( entity, reply_to_msg_id=reply_to, multi_media=media )) return [ @@ -220,7 +220,7 @@ class UploadMethods(MessageParseMethods, UserMethods): if isinstance(update, types.UpdateMessageID) ] - async def upload_file( + def upload_file( self, file, *, part_size_kb=None, file_name=None, use_cache=None, progress_callback=None): """ @@ -337,7 +337,7 @@ class UploadMethods(MessageParseMethods, UserMethods): request = functions.upload.SaveFilePartRequest( file_id, part_index, part) - result = await self(request) + result = self(request) if result: __log__.debug('Uploaded %d/%d', part_index + 1, part_count) @@ -356,7 +356,7 @@ class UploadMethods(MessageParseMethods, UserMethods): # endregion - async def _file_to_media( + def _file_to_media( self, file, force_document=False, progress_callback=None, attributes=None, thumb=None, allow_cache=True, voice_note=False, video_note=False): @@ -387,7 +387,7 @@ class UploadMethods(MessageParseMethods, UserMethods): else: media = types.InputMediaDocumentExternal(file) else: - file_handle = await self.upload_file( + file_handle = self.upload_file( file, progress_callback=progress_callback, use_cache=use_cache if allow_cache else None ) @@ -476,7 +476,7 @@ class UploadMethods(MessageParseMethods, UserMethods): input_kw = {} if thumb: - input_kw['thumb'] = await self.upload_file(thumb) + input_kw['thumb'] = self.upload_file(thumb) media = types.InputMediaUploadedDocument( file=file_handle, diff --git a/telethon/client/users.py b/telethon/client/users.py index 393069ed..88034053 100644 --- a/telethon/client/users.py +++ b/telethon/client/users.py @@ -12,11 +12,11 @@ _NOT_A_REQUEST = TypeError('You can only invoke requests, not types!') class UserMethods(TelegramBaseClient): - async def __call__(self, request, ordered=False): + def __call__(self, request, ordered=False): for r in (request if utils.is_list_like(request) else (request,)): if not isinstance(r, TLRequest): raise _NOT_A_REQUEST - await r.resolve(self, utils) + r.resolve(self, utils) self._last_request = time.time() for _ in range(self._request_retries): @@ -25,12 +25,12 @@ class UserMethods(TelegramBaseClient): if isinstance(future, list): results = [] for f in future: - result = await f + result = f self.session.process_entities(result) results.append(result) return results else: - result = await future + result = future self.session.process_entities(result) return result except (errors.ServerError, errors.RpcCallFailError) as e: @@ -39,7 +39,7 @@ class UserMethods(TelegramBaseClient): except (errors.FloodWaitError, errors.FloodTestPhoneWaitError) as e: if e.seconds <= self.flood_sleep_threshold: __log__.info('Sleeping for %ds on flood wait', e.seconds) - await asyncio.sleep(e.seconds, loop=self._loop) + asyncio.sleep(e.seconds, loop=self._loop) else: raise except (errors.PhoneMigrateError, errors.NetworkMigrateError, @@ -48,15 +48,15 @@ class UserMethods(TelegramBaseClient): should_raise = isinstance(e, ( errors.PhoneMigrateError, errors.NetworkMigrateError )) - if should_raise and await self.is_user_authorized(): + if should_raise and self.is_user_authorized(): raise - await self._switch_dc(e.new_dc) + self._switch_dc(e.new_dc) raise ValueError('Number of retries reached 0') # region Public methods - async def get_me(self, input_peer=False): + def get_me(self, input_peer=False): """ Gets "me" (the self user) which is currently authenticated, or None if the request fails (hence, not authenticated). @@ -74,7 +74,7 @@ class UserMethods(TelegramBaseClient): return self._self_input_peer try: - me = (await self( + me = (self( functions.users.GetUsersRequest([types.InputUserSelf()])))[0] if not self._self_input_peer: @@ -86,7 +86,7 @@ class UserMethods(TelegramBaseClient): except errors.UnauthorizedError: return None - async def is_user_authorized(self): + def is_user_authorized(self): """ Returns ``True`` if the user is authorized. """ @@ -94,12 +94,12 @@ class UserMethods(TelegramBaseClient): return True try: - self._state = await self(functions.updates.GetStateRequest()) + self._state = self(functions.updates.GetStateRequest()) return True except errors.RPCError: return False - async def get_entity(self, entity): + def get_entity(self, entity): """ Turns the given entity into a valid Telegram :tl:`User`, :tl:`Chat` or :tl:`Channel`. You can also pass a list or iterable of entities, @@ -144,7 +144,7 @@ class UserMethods(TelegramBaseClient): if isinstance(x, str): inputs.append(x) else: - inputs.append(await self.get_input_entity(x)) + inputs.append(self.get_input_entity(x)) users = [x for x in inputs if isinstance(x, (types.InputPeerUser, types.InputPeerSelf))] @@ -157,13 +157,13 @@ class UserMethods(TelegramBaseClient): tmp = [] while users: curr, users = users[:200], users[200:] - tmp.extend(await self(functions.users.GetUsersRequest(curr))) + tmp.extend(self(functions.users.GetUsersRequest(curr))) users = tmp if chats: # TODO Handle chats slice? - chats = (await self( + chats = (self( functions.messages.GetChatsRequest(chats))).chats if channels: - channels = (await self( + channels = (self( functions.channels.GetChannelsRequest(channels))).chats # Merge users, chats and channels into a single dictionary @@ -179,7 +179,7 @@ class UserMethods(TelegramBaseClient): result = [] for x in inputs: if isinstance(x, str): - result.append(await self._get_entity_from_string(x)) + result.append(self._get_entity_from_string(x)) elif not isinstance(x, types.InputPeerSelf): result.append(id_entity[utils.get_peer_id(x)]) else: @@ -190,7 +190,7 @@ class UserMethods(TelegramBaseClient): return result[0] if single else result - async def get_input_entity(self, peer): + def get_input_entity(self, peer): """ Turns the given peer into its input entity version. Most requests use this kind of :tl:`InputPeer`, so this is the most suitable call @@ -260,7 +260,7 @@ class UserMethods(TelegramBaseClient): if isinstance(peer, str): return utils.get_input_peer( - await self._get_entity_from_string(peer)) + self._get_entity_from_string(peer)) if not isinstance(peer, int) and (not isinstance(peer, TLObject) or peer.SUBCLASS_OF_ID != 0x2d45687): @@ -280,7 +280,7 @@ class UserMethods(TelegramBaseClient): # region Private methods - async def _get_entity_from_string(self, string): + def _get_entity_from_string(self, string): """ Gets a full entity from the given string, which may be a phone or an username, and processes all the found entities on the session. @@ -294,14 +294,14 @@ class UserMethods(TelegramBaseClient): """ phone = utils.parse_phone(string) if phone: - for user in (await self( + for user in (self( functions.contacts.GetContactsRequest(0))).users: if user.phone == phone: return user else: username, is_join_chat = utils.parse_username(string) if is_join_chat: - invite = await self( + invite = self( functions.messages.CheckChatInviteRequest(username)) if isinstance(invite, types.ChatInvite): @@ -313,10 +313,10 @@ class UserMethods(TelegramBaseClient): return invite.chat elif username: if username in ('me', 'self'): - return await self.get_me() + return self.get_me() try: - result = await self( + result = self( functions.contacts.ResolveUsernameRequest(username)) except errors.UsernameNotOccupiedError as e: raise ValueError('No user has "{}" as username' @@ -328,7 +328,7 @@ class UserMethods(TelegramBaseClient): return entity try: # Nobody with this username, maybe it's an exact name/title - return await self.get_entity( + return self.get_entity( self.session.get_input_entity(string)) except ValueError: pass diff --git a/telethon/crypto/cdndecrypter.py b/telethon/crypto/cdndecrypter.py index dd615a5a..24a4bb49 100644 --- a/telethon/crypto/cdndecrypter.py +++ b/telethon/crypto/cdndecrypter.py @@ -30,7 +30,7 @@ class CdnDecrypter: self.cdn_file_hashes = cdn_file_hashes @staticmethod - async def prepare_decrypter(client, cdn_client, cdn_redirect): + def prepare_decrypter(client, cdn_client, cdn_redirect): """ Prepares a new CDN decrypter. @@ -52,14 +52,14 @@ class CdnDecrypter: cdn_aes, cdn_redirect.cdn_file_hashes ) - cdn_file = await cdn_client(GetCdnFileRequest( + cdn_file = cdn_client(GetCdnFileRequest( file_token=cdn_redirect.file_token, offset=cdn_redirect.cdn_file_hashes[0].offset, limit=cdn_redirect.cdn_file_hashes[0].limit )) if isinstance(cdn_file, CdnFileReuploadNeeded): # We need to use the original client here - await client(ReuploadCdnFileRequest( + client(ReuploadCdnFileRequest( file_token=cdn_redirect.file_token, request_token=cdn_file.request_token )) diff --git a/telethon/events/chataction.py b/telethon/events/chataction.py index 3452889e..c0ec6268 100644 --- a/telethon/events/chataction.py +++ b/telethon/events/chataction.py @@ -166,16 +166,16 @@ class ChatAction(EventBuilder): self.action_message = custom.Message( client, self.action_message, self._entities, None) - async def respond(self, *args, **kwargs): + def respond(self, *args, **kwargs): """ Responds to the chat action message (not as a reply). Shorthand for `telethon.telegram_client.TelegramClient.send_message` with ``entity`` already set. """ - return await self._client.send_message( - await self.get_input_chat(), *args, **kwargs) + return self._client.send_message( + self.get_input_chat(), *args, **kwargs) - async def reply(self, *args, **kwargs): + def reply(self, *args, **kwargs): """ Replies to the chat action message (as a reply). Shorthand for `telethon.telegram_client.TelegramClient.send_message` with @@ -184,13 +184,13 @@ class ChatAction(EventBuilder): Has the same effect as `respond` if there is no message. """ if not self.action_message: - return await self.respond(*args, **kwargs) + return self.respond(*args, **kwargs) kwargs['reply_to'] = self.action_message.id - return await self._client.send_message( - await self.get_input_chat(), *args, **kwargs) + return self._client.send_message( + self.get_input_chat(), *args, **kwargs) - async def delete(self, *args, **kwargs): + def delete(self, *args, **kwargs): """ Deletes the chat action message. You're responsible for checking whether you have the permission to do so, or to except the error @@ -203,12 +203,12 @@ class ChatAction(EventBuilder): if not self.action_message: return - return await self._client.delete_messages( - await self.get_input_chat(), [self.action_message], + return self._client.delete_messages( + self.get_input_chat(), [self.action_message], *args, **kwargs ) - async def get_pinned_message(self): + def get_pinned_message(self): """ If ``new_pin`` is ``True``, this returns the `telethon.tl.custom.message.Message` object that was pinned. @@ -217,8 +217,8 @@ class ChatAction(EventBuilder): return None if isinstance(self._pinned_message, int)\ - and await self.get_input_chat(): - r = await self._client(functions.channels.GetMessagesRequest( + and self.get_input_chat(): + r = self._client(functions.channels.GetMessagesRequest( self._input_chat, [self._pinned_message] )) try: @@ -245,12 +245,12 @@ class ChatAction(EventBuilder): return self._added_by - async def get_added_by(self): + def get_added_by(self): """ Returns `added_by` but will make an API call if necessary. """ if not self.added_by and self._added_by: - self._added_by = await self._client.get_entity(self._added_by) + self._added_by = self._client.get_entity(self._added_by) return self._added_by @@ -266,12 +266,12 @@ class ChatAction(EventBuilder): return self._kicked_by - async def get_kicked_by(self): + def get_kicked_by(self): """ Returns `kicked_by` but will make an API call if necessary. """ if not self.kicked_by and self._kicked_by: - self._kicked_by = await self._client.get_entity(self._kicked_by) + self._kicked_by = self._client.get_entity(self._kicked_by) return self._kicked_by @@ -286,11 +286,11 @@ class ChatAction(EventBuilder): if self.users: return self._users[0] - async def get_user(self): + def get_user(self): """ Returns `user` but will make an API call if necessary. """ - if self.users or await self.get_users(): + if self.users or self.get_users(): return self._users[0] def input_user(self): @@ -300,11 +300,11 @@ class ChatAction(EventBuilder): if self.input_users: return self._input_users[0] - async def get_input_user(self): + def get_input_user(self): """ Returns `input_user` but will make an API call if necessary. """ - if self.input_users or await self.get_input_users(): + if self.input_users or self.get_input_users(): return self._input_users[0] @property @@ -335,7 +335,7 @@ class ChatAction(EventBuilder): return self._users - async def get_users(self): + def get_users(self): """ Returns `users` but will make an API call if necessary. """ @@ -352,7 +352,7 @@ class ChatAction(EventBuilder): missing.append(peer) try: - missing = await self._client.get_entity(missing) + missing = self._client.get_entity(missing) except (TypeError, ValueError): missing = [] @@ -376,7 +376,7 @@ class ChatAction(EventBuilder): pass return self._input_users or [] - async def get_input_users(self): + def get_input_users(self): """ Returns `input_users` but will make an API call if necessary. """ diff --git a/telethon/events/common.py b/telethon/events/common.py index 47235a9d..ae781532 100644 --- a/telethon/events/common.py +++ b/telethon/events/common.py @@ -5,7 +5,7 @@ from .. import utils from ..tl import TLObject, types -async def _into_id_set(client, chats): +def _into_id_set(client, chats): """Helper util to turn the input chat or chats into a set of IDs.""" if chats is None: return None @@ -28,9 +28,9 @@ async def _into_id_set(client, chats): # 0x2d45687 == crc32(b'Peer') result.add(utils.get_peer_id(chat)) else: - chat = await client.get_input_entity(chat) + chat = client.get_input_entity(chat) if isinstance(chat, types.InputPeerSelf): - chat = await client.get_me(input_peer=True) + chat = client.get_me(input_peer=True) result.add(utils.get_peer_id(chat)) return result @@ -60,10 +60,10 @@ class EventBuilder(abc.ABC): def build(self, update): """Builds an event for the given update if possible, or returns None""" - async def resolve(self, client): + def resolve(self, client): """Helper method to allow event builders to be resolved before usage""" - self.chats = await _into_id_set(client, self.chats) - self._self_id = (await client.get_me(input_peer=True)).user_id + self.chats = _into_id_set(client, self.chats) + self._self_id = (client.get_me(input_peer=True)).user_id def _filter_event(self, event): """ @@ -130,7 +130,7 @@ class EventCommon(abc.ABC): return self._input_chat - async def get_input_chat(self): + def get_input_chat(self): """ Returns `input_chat`, but will make an API call to find the input chat unless it's already cached. @@ -138,13 +138,13 @@ class EventCommon(abc.ABC): if self.input_chat is None and self._chat_peer is not None: ch = isinstance(self._chat_peer, types.PeerChannel) if not ch and self._message_id is not None: - msg = await self._client.get_messages( + msg = self._client.get_messages( None, ids=self._message_id) self._chat = msg._chat self._input_chat = msg._input_chat else: target = utils.get_peer_id(self._chat_peer) - async for d in self._client.iter_dialogs(100): + for d in self._client.iter_dialogs(100): if d.id == target: self._chat = d.entity self._input_chat = d.input_entity @@ -179,15 +179,15 @@ class EventCommon(abc.ABC): return self._chat - async def get_chat(self): + def get_chat(self): """ Returns `chat`, but will make an API call to find the chat unless it's already cached. """ - if self.chat is None and await self.get_input_chat(): + if self.chat is None and self.get_input_chat(): try: self._chat =\ - await self._client.get_entity(self._input_chat) + self._client.get_entity(self._input_chat) except ValueError: pass return self._chat diff --git a/telethon/events/messageread.py b/telethon/events/messageread.py index 6e781fa2..0a09cc5c 100644 --- a/telethon/events/messageread.py +++ b/telethon/events/messageread.py @@ -88,7 +88,7 @@ class MessageRead(EventBuilder): """ return self._message_ids - async def get_messages(self): + def get_messages(self): """ Returns the list of `telethon.tl.custom.message.Message` **which contents'** were read. @@ -97,11 +97,11 @@ class MessageRead(EventBuilder): was read instead checking if it's in here. """ if self._messages is None: - chat = await self.get_input_chat() + chat = self.get_input_chat() if not chat: self._messages = [] else: - self._messages = await self._client.get_messages( + self._messages = self._client.get_messages( chat, ids=self._message_ids) return self._messages diff --git a/telethon/events/newmessage.py b/telethon/events/newmessage.py index a43febd5..404a53bf 100644 --- a/telethon/events/newmessage.py +++ b/telethon/events/newmessage.py @@ -71,9 +71,9 @@ class NewMessage(EventBuilder): self.from_users, self.forwards, self.from_users )) - async def resolve(self, client): - await super().resolve(client) - self.from_users = await _into_id_set(client, self.from_users) + def resolve(self, client): + super().resolve(client) + self.from_users = _into_id_set(client, self.from_users) def build(self, update): if isinstance(update, @@ -179,7 +179,7 @@ class NewMessage(EventBuilder): >>> client = TelegramClient(...) >>> >>> @client.on(events.NewMessage(pattern=r'hi (\\w+)!')) - ... async def handler(event): + ... def handler(event): ... # In this case, the result is a ``Match`` object ... # since the ``str`` pattern was converted into ... # the ``re.compile(pattern).match`` function. diff --git a/telethon/events/raw.py b/telethon/events/raw.py index a4a3fc19..5972d45c 100644 --- a/telethon/events/raw.py +++ b/telethon/events/raw.py @@ -22,7 +22,7 @@ class Raw(EventBuilder): assert all(isinstance(x, type) for x in types) self.types = tuple(types) - async def resolve(self, client): + def resolve(self, client): pass def build(self, update): diff --git a/telethon/events/userupdate.py b/telethon/events/userupdate.py index ad2d198e..db40a67f 100644 --- a/telethon/events/userupdate.py +++ b/telethon/events/userupdate.py @@ -152,18 +152,18 @@ class UserUpdate(EventBuilder): """Alias for `chat` (conversation).""" return self.chat - async def get_user(self): + def get_user(self): """Alias for `get_chat` (conversation).""" - return await self.get_chat() + return self.get_chat() @property def input_user(self): """Alias for `input_chat`.""" return self.input_chat - async def get_input_user(self): + def get_input_user(self): """Alias for `get_input_chat`.""" - return await self.get_input_chat() + return self.get_input_chat() @property def user_id(self): diff --git a/telethon/extensions/tcpclient.py b/telethon/extensions/tcpclient.py index a3d68ea7..40bba39b 100644 --- a/telethon/extensions/tcpclient.py +++ b/telethon/extensions/tcpclient.py @@ -71,7 +71,7 @@ class TcpClient: s.setblocking(False) return s - async def connect(self, ip, port): + def connect(self, ip, port): """ Tries connecting to IP:port unless an OSError is raised. @@ -88,7 +88,7 @@ class TcpClient: if self._socket is None: self._socket = self._create_socket(mode, self.proxy) - await asyncio.wait_for( + asyncio.wait_for( self._loop.sock_connect(self._socket, address), timeout=self.timeout, loop=self._loop @@ -122,12 +122,12 @@ class TcpClient: if fd: self._loop.remove_reader(fd) - async def _wait_timeout_or_close(self, coro): + def _wait_timeout_or_close(self, coro): """ Waits for the given coroutine to complete unless the socket is closed or `self.timeout` expires. """ - done, running = await asyncio.wait( + done, running = asyncio.wait( [coro, self._closed.wait()], timeout=self.timeout, return_when=asyncio.FIRST_COMPLETED, @@ -141,7 +141,7 @@ class TcpClient: raise asyncio.TimeoutError() return done.pop().result() - async def write(self, data): + def write(self, data): """ Writes (sends) the specified bytes to the connected peer. :param data: the data to send. @@ -150,14 +150,14 @@ class TcpClient: raise ConnectionResetError('Not connected') try: - await self._wait_timeout_or_close(self.sock_sendall(data)) + self._wait_timeout_or_close(self.sock_sendall(data)) except OSError as e: if e.errno in CONN_RESET_ERRNOS: raise ConnectionResetError() from e else: raise - async def read(self, size): + def read(self, size): """ Reads (receives) a whole block of size bytes from the connected peer. @@ -171,7 +171,7 @@ class TcpClient: bytes_left = size while bytes_left != 0: try: - partial = await self._wait_timeout_or_close( + partial = self._wait_timeout_or_close( self.sock_recv(bytes_left) ) except asyncio.TimeoutError: diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index d79ae7e2..c096cbb9 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -19,7 +19,7 @@ from ..tl.functions import ( ) -async def do_authentication(sender): +def do_authentication(sender): """ Executes the authentication process with the Telegram servers. @@ -28,7 +28,7 @@ async def do_authentication(sender): """ # Step 1 sending: PQ Request, endianness doesn't matter since it's random nonce = int.from_bytes(os.urandom(16), 'big', signed=True) - res_pq = await sender.send(ReqPqMultiRequest(nonce)) + res_pq = sender.send(ReqPqMultiRequest(nonce)) assert isinstance(res_pq, ResPQ), 'Step 1 answer was %s' % res_pq if res_pq.nonce != nonce: @@ -64,7 +64,7 @@ async def do_authentication(sender): ) ) - server_dh_params = await sender.send(ReqDHParamsRequest( + server_dh_params = sender.send(ReqDHParamsRequest( nonce=res_pq.nonce, server_nonce=res_pq.server_nonce, p=p, q=q, @@ -139,7 +139,7 @@ async def do_authentication(sender): client_dh_encrypted = AES.encrypt_ige(client_dh_inner_hashed, key, iv) # Prepare Set client DH params - dh_gen = await sender.send(SetClientDHParamsRequest( + dh_gen = sender.send(SetClientDHParamsRequest( nonce=res_pq.nonce, server_nonce=res_pq.server_nonce, encrypted_data=client_dh_encrypted, diff --git a/telethon/network/connection/common.py b/telethon/network/connection/common.py index a57c248e..9a68a515 100644 --- a/telethon/network/connection/common.py +++ b/telethon/network/connection/common.py @@ -33,7 +33,7 @@ class Connection(abc.ABC): self._timeout = timeout @abc.abstractmethod - async def connect(self, ip, port): + def connect(self, ip, port): raise NotImplementedError @abc.abstractmethod @@ -51,7 +51,7 @@ class Connection(abc.ABC): raise NotImplementedError @abc.abstractmethod - async def close(self): + def close(self): """Closes the connection.""" raise NotImplementedError @@ -64,11 +64,11 @@ class Connection(abc.ABC): ) @abc.abstractmethod - async def recv(self): + def recv(self): """Receives and unpacks a message""" raise NotImplementedError @abc.abstractmethod - async def send(self, message): + def send(self, message): """Encapsulates and sends the given message""" raise NotImplementedError diff --git a/telethon/network/connection/tcpabridged.py b/telethon/network/connection/tcpabridged.py index d5943908..ef52e1a4 100644 --- a/telethon/network/connection/tcpabridged.py +++ b/telethon/network/connection/tcpabridged.py @@ -9,23 +9,23 @@ class ConnectionTcpAbridged(ConnectionTcpFull): only require 1 byte if the packet length is less than 508 bytes (127 << 2, which is very common). """ - async def connect(self, ip, port): - result = await super().connect(ip, port) - await self.conn.write(b'\xef') + def connect(self, ip, port): + result = super().connect(ip, port) + self.conn.write(b'\xef') return result - async def recv(self): - length = struct.unpack('= 127: - length = struct.unpack('> 2 if length < 127: length = struct.pack('B', length) else: length = b'\x7f' + int.to_bytes(length, 3, 'little') - await self.write(length + message) + self.write(length + message) diff --git a/telethon/network/connection/tcpfull.py b/telethon/network/connection/tcpfull.py index c3282123..89d76b69 100644 --- a/telethon/network/connection/tcpfull.py +++ b/telethon/network/connection/tcpfull.py @@ -21,9 +21,9 @@ class ConnectionTcpFull(Connection): self.read = self.conn.read self.write = self.conn.write - async def connect(self, ip, port): + def connect(self, ip, port): try: - await self.conn.connect(ip, port) + self.conn.connect(ip, port) except OSError as e: if e.errno == errno.EISCONN: return # Already connected, no need to re-set everything up @@ -38,13 +38,13 @@ class ConnectionTcpFull(Connection): def is_connected(self): return self.conn.is_connected - async def close(self): + def close(self): self.conn.close() - async def recv(self): - packet_len_seq = await self.read(8) # 4 and 4 + def recv(self): + packet_len_seq = self.read(8) # 4 and 4 packet_len, seq = struct.unpack('= 2: raise ValueError('You can only set either of i, text or filter') - if not await self.get_buttons(): + if not self.get_buttons(): return # Accessing the property sets self._buttons[_flat] if text is not None: if callable(text): for button in self._buttons_flat: if text(button.text): - return await button.click() + return button.click() else: for button in self._buttons_flat: if button.text == text: - return await button.click() + return button.click() return if filter is not None: for button in self._buttons_flat: if filter(button): - return await button.click() + return button.click() return if i is None: i = 0 if j is None: - return await self._buttons_flat[i].click() + return self._buttons_flat[i].click() else: - return await self._buttons[i][j].click() + return self._buttons[i][j].click() class _CustomMessage(Message, types.Message): diff --git a/telethon/tl/custom/messagebutton.py b/telethon/tl/custom/messagebutton.py index 1bfc53a4..1ba4bcfb 100644 --- a/telethon/tl/custom/messagebutton.py +++ b/telethon/tl/custom/messagebutton.py @@ -52,7 +52,7 @@ class MessageButton: if isinstance(self.button, types.KeyboardButtonUrl): return self.button.url - async def click(self): + def click(self): """ Emulates the behaviour of clicking this button. @@ -70,18 +70,18 @@ class MessageButton: be passed to ``webbrowser.open`` and return ``True`` on success. """ if isinstance(self.button, types.KeyboardButton): - return await self._client.send_message( + return self._client.send_message( self._chat, self.button.text, reply_to=self._msg_id) elif isinstance(self.button, types.KeyboardButtonCallback): req = functions.messages.GetBotCallbackAnswerRequest( peer=self._chat, msg_id=self._msg_id, data=self.button.data ) try: - return await self._client(req) + return self._client(req) except BotTimeout: return None elif isinstance(self.button, types.KeyboardButtonSwitchInline): - return await self._client(functions.messages.StartBotRequest( + return self._client(functions.messages.StartBotRequest( bot=self._bot, peer=self._chat, start_param=self.button.query )) elif isinstance(self.button, types.KeyboardButtonUrl): diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index 3eb24eb7..f130cf07 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -162,5 +162,5 @@ class TLRequest(TLObject): def read_result(reader): return reader.tgread_object() - async def resolve(self, client, utils): + def resolve(self, client, utils): pass diff --git a/telethon_examples/assistant.py b/telethon_examples/assistant.py deleted file mode 100644 index 8fadd239..00000000 --- a/telethon_examples/assistant.py +++ /dev/null @@ -1,218 +0,0 @@ -import asyncio -import difflib -import logging -import os -import sys -import time -import urllib.parse - -from telethon import TelegramClient, events, custom - -logging.basicConfig(level=logging.WARNING) -logging.getLogger('asyncio').setLevel(logging.ERROR) - -for x in 'TG_API_ID TG_API_HASH TG_TOKEN'.split(): - if x not in os.environ: - print(f'{x} not in environmental variables', file=sys.stderr) - quit() - -NAME = os.environ['TG_TOKEN'].split(':')[0] -bot = TelegramClient(NAME, os.environ['TG_API_ID'], os.environ['TG_API_HASH']) - - -# ============================== Constants ============================== -WELCOME = ( - 'Hi and welcome to the group. Before asking any questions, **please** ' - 'read [the docs](https://telethon.readthedocs.io/). Make sure you are ' - 'using the latest version with `pip3 install -U telethon`, since most ' - 'problems have already been fixed in newer versions.' -) - -READ_FULL = ( - 'Please read [Accessing the Full API](https://telethon.readthedocs.io' - '/en/latest/extra/advanced-usage/accessing-the-full-api.html)' -) - -SEARCH = ( - 'Remember [search is your friend]' - '(https://lonamiwebs.github.io/Telethon/?q={})' -) - -DOCS = 'TL Reference for [{}](https://lonamiwebs.github.io/Telethon/?q={})' -RTD = '[Read The Docs!](https://telethon.readthedocs.io)' -RTFD = '[Read The F* Docs!](https://telethon.readthedocs.io)' -DOCS_CLIENT = 'https://telethon.readthedocs.io/en/latest/telethon.client.html#' -DOCS_MESSAGE = ( - 'https://telethon.readthedocs.io/en/latest/' - 'telethon.tl.custom.html#telethon.tl.custom.message.Message.' -) -# ============================== Constants ============================== -# ============================== Welcome ============================== -last_welcome = None - - -@bot.on(events.ChatAction) -async def handler(event): - if event.user_joined: - global last_welcome - if last_welcome is not None: - await last_welcome.delete() - - last_welcome = await event.reply(WELCOME) - - -# ============================== Welcome ============================== -# ============================== Commands ============================== - - -@bot.on(events.NewMessage(pattern='#ping', forwards=False)) -async def handler(event): - s = time.time() - message = await event.reply('Pong!') - d = time.time() - s - await message.edit(f'Pong! __(reply took {d:.2f}s)__') - await asyncio.sleep(5) - await asyncio.wait([event.delete(), message.delete()]) - - -@bot.on(events.NewMessage(pattern='#full', forwards=False)) -async def handler(event): - """#full: Advises to read "Accessing the full API" in the docs.""" - await asyncio.wait([ - event.delete(), - event.respond(READ_FULL, reply_to=event.reply_to_msg_id) - ]) - - -@bot.on(events.NewMessage(pattern='#search (.+)', forwards=False)) -async def handler(event): - """#search query: Searches for "query" in the method reference.""" - query = urllib.parse.quote(event.pattern_match.group(1)) - await asyncio.wait([ - event.delete(), - event.respond(SEARCH.format(query), reply_to=event.reply_to_msg_id) - ]) - - -@bot.on(events.NewMessage(pattern='(?i)#(?:docs|ref) (.+)', forwards=False)) -async def handler(event): - """#docs or #ref query: Like #search but shows the query.""" - q1 = event.pattern_match.group(1) - q2 = urllib.parse.quote(q1) - await asyncio.wait([ - event.delete(), - event.respond(DOCS.format(q1, q2), reply_to=event.reply_to_msg_id) - ]) - - -@bot.on(events.NewMessage(pattern='#rt(f)?d', forwards=False)) -async def handler(event): - """#rtd: Tells the user to please read the docs.""" - rtd = RTFD if event.pattern_match.group(1) else RTD - await asyncio.wait([ - event.delete(), - event.respond(rtd, reply_to=event.reply_to_msg_id) - ]) - - -@bot.on(events.NewMessage(pattern='(?i)#(client|msg) (.+)', forwards=False)) -async def handler(event): - """#client or #msg query: Looks for the given attribute in RTD.""" - await event.delete() - query = event.pattern_match.group(2).lower() - cls = ({'client': TelegramClient, 'msg': custom.Message} - [event.pattern_match.group(1)]) - - attr = search_attr(cls, query) - if not attr: - await event.respond(f'No such method "{query}" :/') - return - - name = attr - if event.pattern_match.group(1) == 'client': - attr = attr_fullname(cls, attr) - url = DOCS_CLIENT - elif event.pattern_match.group(1) == 'msg': - name = f'Message.{name}' - url = DOCS_MESSAGE - else: - return - - await event.respond( - f'Documentation for [{name}]({url}{attr})', - reply_to=event.reply_to_msg_id - ) - - -@bot.on(events.NewMessage(pattern='#list', forwards=False)) -async def handler(event): - await event.delete() - text = 'Available commands:\n' - for callback, handler in bot.list_event_handlers(): - if isinstance(handler, events.NewMessage) and callback.__doc__: - text += f'\n{callback.__doc__}' - - message = await event.respond(text) - await asyncio.sleep(1 * text.count(' ')) # Sleep ~1 second per word - await message.delete() - - -# ============================== Commands ============================== -# ============================== AutoReply ============================== - - -@bot.on(events.NewMessage(pattern='(?i)how (.+?)[\W]*$', forwards=False)) -@bot.on(events.NewMessage(pattern='(.+?)[\W]*?\?+', forwards=False)) -async def handler(event): - words = event.pattern_match.group(1).split() - rates = [ - search_attr(TelegramClient, ' '.join(words[-i:]), threshold=None) - for i in range(1, 4) - ] - what = max(rates, key=lambda t: t[1]) - if what[1] < 0.7: - return - - name = what[0] - attr = attr_fullname(TelegramClient, name) - await event.reply( - f'Documentation for [{name}]({DOCS_CLIENT}{attr})', - reply_to=event.reply_to_msg_id - ) - - # We have two @client.on, both could fire, stop stop that - raise events.StopPropagation - - -# ============================== AutoReply ============================== -# ============================== Helpers ============================== - - -def search_attr(cls, query, threshold=0.6): - seq = difflib.SequenceMatcher(b=query, autojunk=False) - scores = [] - for n in dir(cls): - if not n.startswith('_'): - seq.set_seq1(n) - scores.append((n, seq.ratio())) - - scores.sort(key=lambda t: t[1], reverse=True) - if threshold is None: - return scores[0] - else: - return scores[0][0] if scores[0][1] >= threshold else None - - -def attr_fullname(cls, n): - m = getattr(cls, n) - cls = sys.modules.get(m.__module__) - for name in m.__qualname__.split('.')[:-1]: - cls = getattr(cls, name) - return cls.__module__ + '.' + cls.__name__ + '.' + m.__name__ - - -# ============================== Helpers ============================== - - -bot.start(bot_token=os.environ['TG_TOKEN']) -bot.run_until_disconnected() diff --git a/telethon_examples/interactive_telegram_client.py b/telethon_examples/interactive_telegram_client.py deleted file mode 100644 index 382ca0cd..00000000 --- a/telethon_examples/interactive_telegram_client.py +++ /dev/null @@ -1,382 +0,0 @@ -import os -import sys -import asyncio -from getpass import getpass - -from telethon.utils import get_display_name - -from telethon import TelegramClient, events -from telethon.network import ConnectionTcpAbridged -from telethon.errors import SessionPasswordNeededError - - -# Create a global variable to hold the loop we will be using -loop = asyncio.get_event_loop() - - -def sprint(string, *args, **kwargs): - """Safe Print (handle UnicodeEncodeErrors on some terminals)""" - try: - print(string, *args, **kwargs) - except UnicodeEncodeError: - string = string.encode('utf-8', errors='ignore')\ - .decode('ascii', errors='ignore') - print(string, *args, **kwargs) - - -def print_title(title): - """Helper function to print titles to the console more nicely""" - sprint('\n') - sprint('=={}=='.format('=' * len(title))) - sprint('= {} ='.format(title)) - sprint('=={}=='.format('=' * len(title))) - - -def bytes_to_string(byte_count): - """Converts a byte count to a string (in KB, MB...)""" - suffix_index = 0 - while byte_count >= 1024: - byte_count /= 1024 - suffix_index += 1 - - return '{:.2f}{}'.format( - byte_count, [' bytes', 'KB', 'MB', 'GB', 'TB'][suffix_index] - ) - - -async def async_input(prompt): - """ - Python's ``input()`` is blocking, which means the event loop we set - above can't be running while we're blocking there. This method will - let the loop run while we wait for input. - """ - print(prompt, end='', flush=True) - return (await loop.run_in_executor(None, sys.stdin.readline)).rstrip() - - -class InteractiveTelegramClient(TelegramClient): - """Full featured Telegram client, meant to be used on an interactive - session to see what Telethon is capable off - - - This client allows the user to perform some basic interaction with - Telegram through Telethon, such as listing dialogs (open chats), - talking to people, downloading media, and receiving updates. - """ - - def __init__(self, session_user_id, user_phone, api_id, api_hash, - proxy=None): - """ - Initializes the InteractiveTelegramClient. - :param session_user_id: Name of the *.session file. - :param user_phone: The phone of the user that will login. - :param api_id: Telegram's api_id acquired through my.telegram.org. - :param api_hash: Telegram's api_hash. - :param proxy: Optional proxy tuple/dictionary. - """ - print_title('Initialization') - - print('Initializing interactive example...') - - # The first step is to initialize the TelegramClient, as we are - # subclassing it, we need to call super().__init__(). On a more - # normal case you would want 'client = TelegramClient(...)' - super().__init__( - # These parameters should be passed always, session name and API - session_user_id, api_id, api_hash, - - # You can optionally change the connection mode by passing a - # type or an instance of it. This changes how the sent packets - # look (low-level concept you normally shouldn't worry about). - # Default is ConnectionTcpFull, smallest is ConnectionTcpAbridged. - connection=ConnectionTcpAbridged, - - # If you're using a proxy, set it here. - proxy=proxy - ) - - # Store {message.id: message} map here so that we can download - # media known the message ID, for every message having media. - self.found_media = {} - - # Calling .connect() may raise a connection error False, so you need - # to except those before continuing. Otherwise you may want to retry - # as done here. - print('Connecting to Telegram servers...') - try: - loop.run_until_complete(self.connect()) - except ConnectionError: - print('Initial connection failed. Retrying...') - loop.run_until_complete(self.connect()) - - # If the user hasn't called .sign_in() or .sign_up() yet, they won't - # be authorized. The first thing you must do is authorize. Calling - # .sign_in() should only be done once as the information is saved on - # the *.session file so you don't need to enter the code every time. - if not loop.run_until_complete(self.is_user_authorized()): - print('First run. Sending code request...') - loop.run_until_complete(self.sign_in(user_phone)) - - self_user = None - while self_user is None: - code = input('Enter the code you just received: ') - try: - self_user =\ - loop.run_until_complete(self.sign_in(code=code)) - - # Two-step verification may be enabled, and .sign_in will - # raise this error. If that's the case ask for the password. - # Note that getpass() may not work on PyCharm due to a bug, - # if that's the case simply change it for input(). - except SessionPasswordNeededError: - pw = getpass('Two step verification is enabled. ' - 'Please enter your password: ') - - self_user =\ - loop.run_until_complete(self.sign_in(password=pw)) - - async def run(self): - """Main loop of the TelegramClient, will wait for user action""" - - # Once everything is ready, we can add an event handler. - # - # Events are an abstraction over Telegram's "Updates" and - # are much easier to use. - self.add_event_handler(self.message_handler, events.NewMessage) - - # Enter a while loop to chat as long as the user wants - while True: - # Retrieve the top dialogs. You can set the limit to None to - # retrieve all of them if you wish, but beware that may take - # a long time if you have hundreds of them. - dialog_count = 15 - - # Entities represent the user, chat or channel - # corresponding to the dialog on the same index. - dialogs = await self.get_dialogs(limit=dialog_count) - - i = None - while i is None: - print_title('Dialogs window') - - # Display them so the user can choose - for i, dialog in enumerate(dialogs, start=1): - sprint('{}. {}'.format(i, get_display_name(dialog.entity))) - - # Let the user decide who they want to talk to - print() - print('> Who do you want to send messages to?') - print('> Available commands:') - print(' !q: Quits the dialogs window and exits.') - print(' !l: Logs out, terminating this session.') - print() - i = await async_input('Enter dialog ID or a command: ') - if i == '!q': - return - if i == '!l': - # Logging out will cause the user to need to reenter the - # code next time they want to use the library, and will - # also delete the *.session file off the filesystem. - # - # This is not the same as simply calling .disconnect(), - # which simply shuts down everything gracefully. - await self.log_out() - return - - try: - i = int(i if i else 0) - 1 - # Ensure it is inside the bounds, otherwise retry - if not 0 <= i < dialog_count: - i = None - except ValueError: - i = None - - # Retrieve the selected user (or chat, or channel) - entity = dialogs[i].entity - - # Show some information - print_title('Chat with "{}"'.format(get_display_name(entity))) - print('Available commands:') - print(' !q: Quits the current chat.') - print(' !Q: Quits the current chat and exits.') - print(' !h: prints the latest messages (message History).') - print(' !up : Uploads and sends the Photo from path.') - print(' !uf : Uploads and sends the File from path.') - print(' !d : Deletes a message by its id') - print(' !dm : Downloads the given message Media (if any).') - print(' !dp: Downloads the current dialog Profile picture.') - print(' !i: Prints information about this chat..') - print() - - # And start a while loop to chat - while True: - msg = await async_input('Enter a message: ') - # Quit - if msg == '!q': - break - elif msg == '!Q': - return - - # History - elif msg == '!h': - # First retrieve the messages and some information - messages = await self.get_messages(entity, limit=10) - - # Iterate over all (in reverse order so the latest appear - # the last in the console) and print them with format: - # "[hh:mm] Sender: Message" - for msg in reversed(messages): - # Note how we access .sender here. Since we made an - # API call using the self client, it will always have - # information about the sender. This is different to - # events, where Telegram may not always send the user. - name = get_display_name(msg.sender) - - # Format the message content - if getattr(msg, 'media', None): - self.found_media[msg.id] = msg - content = '<{}> {}'.format( - type(msg.media).__name__, msg.message) - - elif hasattr(msg, 'message'): - content = msg.message - elif hasattr(msg, 'action'): - content = str(msg.action) - else: - # Unknown message, simply print its class name - content = type(msg).__name__ - - # And print it to the user - sprint('[{}:{}] (ID={}) {}: {}'.format( - msg.date.hour, msg.date.minute, msg.id, name, content)) - - # Send photo - elif msg.startswith('!up '): - # Slice the message to get the path - path = msg[len('!up '):] - await self.send_photo(path=path, entity=entity) - - # Send file (document) - elif msg.startswith('!uf '): - # Slice the message to get the path - path = msg[len('!uf '):] - await self.send_document(path=path, entity=entity) - - # Delete messages - elif msg.startswith('!d '): - # Slice the message to get message ID - msg = msg[len('!d '):] - deleted_msg = await self.delete_messages(entity, msg) - print('Deleted {}'.format(deleted_msg)) - - # Download media - elif msg.startswith('!dm '): - # Slice the message to get message ID - await self.download_media_by_id(msg[len('!dm '):]) - - # Download profile photo - elif msg == '!dp': - print('Downloading profile picture to usermedia/...') - os.makedirs('usermedia', exist_ok=True) - output = await self.download_profile_photo(entity, - 'usermedia') - if output: - print('Profile picture downloaded to', output) - else: - print('No profile picture found for this user!') - - elif msg == '!i': - attributes = list(entity.to_dict().items()) - pad = max(len(x) for x, _ in attributes) - for name, val in attributes: - print("{:<{width}} : {}".format(name, val, width=pad)) - - # Send chat message (if any) - elif msg: - await self.send_message(entity, msg, link_preview=False) - - async def send_photo(self, path, entity): - """Sends the file located at path to the desired entity as a photo""" - await self.send_file( - entity, path, - progress_callback=self.upload_progress_callback - ) - print('Photo sent!') - - async def send_document(self, path, entity): - """Sends the file located at path to the desired entity as a document""" - await self.send_file( - entity, path, - force_document=True, - progress_callback=self.upload_progress_callback - ) - print('Document sent!') - - async def download_media_by_id(self, media_id): - """Given a message ID, finds the media this message contained and - downloads it. - """ - try: - msg = self.found_media[int(media_id)] - except (ValueError, KeyError): - # ValueError when parsing, KeyError when accessing dictionary - print('Invalid media ID given or message not found!') - return - - print('Downloading media to usermedia/...') - os.makedirs('usermedia', exist_ok=True) - output = await self.download_media( - msg.media, - file='usermedia/', - progress_callback=self.download_progress_callback - ) - print('Media downloaded to {}!'.format(output)) - - @staticmethod - def download_progress_callback(downloaded_bytes, total_bytes): - InteractiveTelegramClient.print_progress( - 'Downloaded', downloaded_bytes, total_bytes - ) - - @staticmethod - def upload_progress_callback(uploaded_bytes, total_bytes): - InteractiveTelegramClient.print_progress( - 'Uploaded', uploaded_bytes, total_bytes - ) - - @staticmethod - def print_progress(progress_type, downloaded_bytes, total_bytes): - print('{} {} out of {} ({:.2%})'.format( - progress_type, bytes_to_string(downloaded_bytes), - bytes_to_string(total_bytes), downloaded_bytes / total_bytes) - ) - - async def message_handler(self, event): - """Callback method for received events.NewMessage""" - - # Note that message_handler is called when a Telegram update occurs - # and an event is created. Telegram may not always send information - # about the ``.sender`` or the ``.chat``, so if you *really* want it - # you should use ``get_chat()`` and ``get_sender()`` while working - # with events. Since they are methods, you know they may make an API - # call, which can be expensive. - chat = await event.get_chat() - if event.is_group: - if event.out: - sprint('>> sent "{}" to chat {}'.format( - event.text, get_display_name(chat) - )) - else: - sprint('<< {} @ {} sent "{}"'.format( - get_display_name(await event.get_sender()), - get_display_name(chat), - event.text - )) - else: - if event.out: - sprint('>> "{}" to user {}'.format( - event.text, get_display_name(chat) - )) - else: - sprint('<< {} sent "{}"'.format( - get_display_name(chat), event.text - )) diff --git a/telethon_examples/print_updates.py b/telethon_examples/print_updates.py deleted file mode 100755 index d1e70ceb..00000000 --- a/telethon_examples/print_updates.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -# A simple script to print all updates received -# -# NOTE: To run this script you MUST have 'TG_API_ID' and 'TG_API_HASH' in -# your environment variables. This is a good way to use these private -# values. See https://superuser.com/q/284342. -from os import environ - -from telethon import TelegramClient - - -client = TelegramClient( - environ.get('TG_SESSION', 'session'), - environ['TG_API_ID'], - environ['TG_API_HASH'], - proxy=None -) - - -async def update_handler(update): - print(update) - - -client.add_event_handler(update_handler) - -'''You could also have used the @client.on(...) syntax: -from telethon import events - -@client.on(events.Raw) -async def update_handler(update): - print(update) -''' - -with client.start(): - print('(Press Ctrl+C to stop this)') - client.run_until_disconnected() diff --git a/telethon_examples/replier.py b/telethon_examples/replier.py deleted file mode 100755 index 0ba2a214..00000000 --- a/telethon_examples/replier.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -""" -A example script to automatically send messages based on certain triggers. - -NOTE: To run this script you MUST have 'TG_API_ID' and 'TG_API_HASH' in - your environment variables. This is a good way to use these private - values. See https://superuser.com/q/284342. - -This script assumes that you have certain files on the working directory, -such as "xfiles.m4a" or "anytime.png" for some of the automated replies. -""" -import re -from collections import defaultdict -from datetime import datetime, timedelta -from os import environ - -from telethon import TelegramClient, events - -"""Uncomment this for debugging -import logging -logging.basicConfig(level=logging.DEBUG) -logging.debug('dbg') -logging.info('info') -""" - -REACTS = {'emacs': 'Needs more vim', - 'chrome': 'Needs more Firefox'} - -# A list of dates of reactions we've sent, so we can keep track of floods -recent_reacts = defaultdict(list) - - -# TG_API_ID and TG_API_HASH *must* exist or this won't run! -session_name = environ.get('TG_SESSION', 'session') -client = TelegramClient( - session_name, int(environ['TG_API_ID']), environ['TG_API_HASH'], - proxy=None -) - - -@client.on(events.NewMessage) -async def my_handler(event: events.NewMessage.Event): - global recent_reacts - - # Through event.raw_text we access the text of messages without format - words = re.split('\W+', event.raw_text) - - # Try to match some reaction - for trigger, response in REACTS.items(): - if len(recent_reacts[event.chat_id]) > 3: - # Silently ignore triggers if we've recently sent 3 reactions - break - - if trigger in words: - # Remove recent replies older than 10 minutes - recent_reacts[event.chat_id] = [ - a for a in recent_reacts[event.chat_id] if - datetime.now() - a < timedelta(minutes=10) - ] - # Send a reaction as a reply (otherwise, event.respond()) - await event.reply(response) - # Add this reaction to the list of recent actions - recent_reacts[event.chat_id].append(datetime.now()) - - # Automatically send relevant media when we say certain things - # When invoking requests, get_input_entity needs to be called manually - if event.out: - chat = await event.get_input_chat() - if event.raw_text.lower() == 'x files theme': - await client.send_file(chat, 'xfiles.m4a', - reply_to=event.message.id, voice_note=True) - if event.raw_text.lower() == 'anytime': - await client.send_file(chat, 'anytime.png', - reply_to=event.message.id) - if '.shrug' in event.text: - await event.edit(event.text.replace('.shrug', r'¯\_(ツ)_/¯')) - - -with client.start(): - print('(Press Ctrl+C to stop this)') - client.run_until_disconnected() diff --git a/telethon_generator/generators/tlobject.py b/telethon_generator/generators/tlobject.py index 111b0c75..afa6bcdb 100644 --- a/telethon_generator/generators/tlobject.py +++ b/telethon_generator/generators/tlobject.py @@ -15,13 +15,13 @@ AUTO_GEN_NOTICE = \ AUTO_CASTS = { 'InputPeer': - 'utils.get_input_peer(await client.get_input_entity({}))', + 'utils.get_input_peer(client.get_input_entity({}))', 'InputChannel': - 'utils.get_input_channel(await client.get_input_entity({}))', + 'utils.get_input_channel(client.get_input_entity({}))', 'InputUser': - 'utils.get_input_user(await client.get_input_entity({}))', + 'utils.get_input_user(client.get_input_entity({}))', 'InputDialogPeer': - 'utils.get_input_dialog(await client.get_input_entity({}))', + 'utils.get_input_dialog(client.get_input_entity({}))', 'InputMedia': 'utils.get_input_media({})', 'InputPhoto': 'utils.get_input_photo({})', @@ -233,7 +233,7 @@ def _write_class_init(tlobject, kind, type_constructors, builder): def _write_resolve(tlobject, builder): if any(arg.type in AUTO_CASTS for arg in tlobject.real_args): - builder.writeln('async def resolve(self, client, utils):') + builder.writeln('def resolve(self, client, utils):') for arg in tlobject.real_args: ac = AUTO_CASTS.get(arg.type, None) if not ac: diff --git a/telethon_tests/__init__.py b/telethon_tests/__init__.py deleted file mode 100644 index 64bb92a6..00000000 --- a/telethon_tests/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .crypto_test import CryptoTests -from .network_test import NetworkTests -from .parser_test import ParserTests -from .tl_test import TLTests -from .utils_test import UtilsTests diff --git a/telethon_tests/test_crypto.py b/telethon_tests/test_crypto.py deleted file mode 100644 index 136e6091..00000000 --- a/telethon_tests/test_crypto.py +++ /dev/null @@ -1,143 +0,0 @@ -import unittest -from hashlib import sha1 - -import telethon.helpers as utils -from telethon.crypto import AES, Factorization -# from crypto.PublicKey import RSA as PyCryptoRSA - - -class CryptoTests(unittest.TestCase): - def setUp(self): - # Test known values - self.key = b'\xd1\xf4MXy\x0c\xf8/z,\xe9\xf9\xa4\x17\x04\xd9C\xc9\xaba\x81\xf3\xf8\xdd\xcb\x0c6\x92\x01\x1f\xc2y' - self.iv = b':\x02\x91x\x90Dj\xa6\x03\x90C\x08\x9e@X\xb5E\xffwy\xf3\x1c\xde\xde\xfbo\x8dm\xd6e.Z' - - self.plain_text = b'Non encrypted text :D' - self.plain_text_padded = b'My len is more uniform, promise!' - - self.cipher_text = b'\xb6\xa7\xec.\xb9\x9bG\xcb\xe9{\x91[\x12\xfc\x84D\x1c' \ - b'\x93\xd9\x17\x03\xcd\xd6\xb1D?\x98\xd2\xb5\xa5U\xfd' - - self.cipher_text_padded = b"W\xd1\xed'\x01\xa6c\xc3\xcb\xef\xaa\xe5\x1d\x1a" \ - b"[\x1b\xdf\xcdI\x1f>Z\n\t\xb9\xd2=\xbaF\xd1\x8e'" - - def test_sha1(self): - string = 'Example string' - - hash_sum = sha1(string.encode('utf-8')).digest() - expected = b'\nT\x92|\x8d\x06:)\x99\x04\x8e\xf8j?\xc4\x8e\xd3}m9' - - self.assertEqual(hash_sum, expected, - msg='Invalid sha1 hash_sum representation (should be {}, but is {})' - .format(expected, hash_sum)) - - @unittest.skip("test_aes_encrypt needs fix") - def test_aes_encrypt(self): - value = AES.encrypt_ige(self.plain_text, self.key, self.iv) - take = 16 # Don't take all the bytes, since latest involve are random padding - self.assertEqual(value[:take], self.cipher_text[:take], - msg='Ciphered text ("{}") does not equal expected ("{}")' - .format(value[:take], self.cipher_text[:take])) - - value = AES.encrypt_ige(self.plain_text_padded, self.key, self.iv) - self.assertEqual(value, self.cipher_text_padded, - msg='Ciphered text ("{}") does not equal expected ("{}")' - .format(value, self.cipher_text_padded)) - - def test_aes_decrypt(self): - # The ciphered text must always be padded - value = AES.decrypt_ige(self.cipher_text_padded, self.key, self.iv) - self.assertEqual(value, self.plain_text_padded, - msg='Decrypted text ("{}") does not equal expected ("{}")' - .format(value, self.plain_text_padded)) - - @unittest.skip("test_calc_key needs fix") - def test_calc_key(self): - # TODO Upgrade test for MtProto 2.0 - shared_key = b'\xbc\xd2m\xb7\xcav\xf4][\x88\x83\' \xf3\x11\x8as\xd04\x941\xae' \ - b'*O\x03\x86\x9a/H#\x1a\x8c\xb5j\xe9$\xe0IvCm^\xe70\x1a5C\t\x16' \ - b'\x03\xd2\x9d\xa9\x89\xd6\xce\x08P\x0fdr\xa0\xb3\xeb\xfecv\x1a' \ - b'\xdfJ\x14\x96\x98\x16\xa3G\xab\x04\x14!\\\xeb\n\xbcn\xdf\xc4%' \ - b'\xc6\t\xb7\x16\x14\x9c\'\x81\x15=\xb0\xaf\x0e\x0bR\xaa\x0466s' \ - b'\xf0\xcf\xb7\xb8>,D\x94x\xd7\xf8\xe0\x84\xcb%\xd3\x05\xb2\xe8' \ - b'\x95Mr?\xa2\xe8In\xf9\x0b[E\x9b\xaa\x0cX\x7f\x0ei\xde\xeed\x1d' \ - b'x/J\xce\xea^}0;\xa83B\xbbR\xa1\xbfe\x04\xb9\x1e\xa1"f=\xa5M@' \ - b'\x9e\xdd\x81\x80\xc9\xa5\xfb\xfcg\xdd\x15\x03p!\x0ffD\x16\x892' \ - b'\xea\xca\xb1A\x99O\xa94P\xa9\xa2\xc6;\xb2C9\x1dC5\xd2\r\xecL' \ - b'\xd9\xabw-\x03\ry\xc2v\x17]\x02\x15\x0cBa\x97\xce\xa5\xb1\xe4]' \ - b'\x8e\xe0,\xcfC{o\xfa\x99f\xa4pM\x00' - - # Calculate key being the client - msg_key = b'\xba\x1a\xcf\xda\xa8^Cbl\xfa\xb6\x0c:\x9b\xb0\xfc' - - key, iv = utils.calc_key(shared_key, msg_key, client=True) - expected_key = b"\xaf\xe3\x84Qm\xe0!\x0c\xd91\xe4\x9a\xa0v_gc" \ - b"x\xa1\xb0\xc9\xbc\x16'v\xcf,\x9dM\xae\xc6\xa5" - - expected_iv = b'\xb8Q\xf3\xc5\xa3]\xc6\xdf\x9e\xe0Q\xbd"\x8d' \ - b'\x13\t\x0e\x9a\x9d^8\xa2\xf8\xe7\x00w\xd9\xc1' \ - b'\xa7\xa0\xf7\x0f' - - self.assertEqual(key, expected_key, - msg='Invalid key (expected ("{}"), got ("{}"))' - .format(expected_key, key)) - self.assertEqual(iv, expected_iv, - msg='Invalid IV (expected ("{}"), got ("{}"))' - .format(expected_iv, iv)) - - # Calculate key being the server - msg_key = b'\x86m\x92i\xcf\x8b\x93\xaa\x86K\x1fi\xd04\x83]' - - key, iv = utils.calc_key(shared_key, msg_key, client=False) - expected_key = b'\xdd0X\xb6\x93\x8e\xc9y\xef\x83\xf8\x8cj' \ - b'\xa7h\x03\xe2\xc6\xb16\xc5\xbb\xfc\xe7' \ - b'\xdf\xd6\xb1g\xf7u\xcfk' - - expected_iv = b'\xdcL\xc2\x18\x01J"X\x86lb\xb6\xb547\xfd' \ - b'\xe2a4\xb6\xaf}FS\xd7[\xe0N\r\x19\xfb\xbc' - - self.assertEqual(key, expected_key, - msg='Invalid key (expected ("{}"), got ("{}"))' - .format(expected_key, key)) - self.assertEqual(iv, expected_iv, - msg='Invalid IV (expected ("{}"), got ("{}"))' - .format(expected_iv, iv)) - - def test_generate_key_data_from_nonce(self): - server_nonce = int.from_bytes(b'The 16-bit nonce', byteorder='little') - new_nonce = int.from_bytes(b'The new, calculated 32-bit nonce', byteorder='little') - - key, iv = utils.generate_key_data_from_nonce(server_nonce, new_nonce) - expected_key = b'/\xaa\x7f\xa1\xfcs\xef\xa0\x99zh\x03M\xa4\x8e\xb4\xab\x0eE]b\x95|\xfe\xc0\xf8\x1f\xd4\xa0\xd4\xec\x91' - expected_iv = b'\xf7\xae\xe3\xc8+=\xc2\xb8\xd1\xe1\x1b\x0e\x10\x07\x9fn\x9e\xdc\x960\x05\xf9\xea\xee\x8b\xa1h The ' - - self.assertEqual(key, expected_key, - msg='Key ("{}") does not equal expected ("{}")' - .format(key, expected_key)) - self.assertEqual(iv, expected_iv, - msg='IV ("{}") does not equal expected ("{}")' - .format(iv, expected_iv)) - - # test_fringerprint_from_key can't be skipped due to ImportError - # def test_fingerprint_from_key(self): - # assert rsa._compute_fingerprint(PyCryptoRSA.importKey( - # '-----BEGIN RSA PUBLIC KEY-----\n' - # 'MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n' - # 'lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\n' - # 'an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\n' - # 'Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n' - # '8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n' - # 'Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n' - # '-----END RSA PUBLIC KEY-----' - # )) == b'!k\xe8l\x02+\xb4\xc3', 'Wrong fingerprint calculated' - - def test_factorize(self): - pq = 3118979781119966969 - p, q = Factorization.factorize(pq) - if p > q: - p, q = q, p - - self.assertEqual(p, 1719614201, - msg='Factorized pair did not yield the correct result') - self.assertEqual(q, 1813767169, - msg='Factorized pair did not yield the correct result') diff --git a/telethon_tests/test_higher_level.py b/telethon_tests/test_higher_level.py deleted file mode 100644 index 67fac515..00000000 --- a/telethon_tests/test_higher_level.py +++ /dev/null @@ -1,49 +0,0 @@ -import unittest -import os -from io import BytesIO -from random import randint -from hashlib import sha256 -from telethon import TelegramClient - -# Fill in your api_id and api_hash when running the tests -# and REMOVE THEM once you've finished testing them. -api_id = None -api_hash = None - - -class HigherLevelTests(unittest.TestCase): - def setUp(self): - if not api_id or not api_hash: - raise ValueError('Please fill in both your api_id and api_hash.') - - @unittest.skip("you can't seriously trash random mobile numbers like that :)") - def test_cdn_download(self): - client = TelegramClient(None, api_id, api_hash) - client.session.set_dc(0, '149.154.167.40', 80) - self.assertTrue(client.connect()) - - try: - phone = '+999662' + str(randint(0, 9999)).zfill(4) - client.send_code_request(phone) - client.sign_up('22222', 'Test', 'DC') - - me = client.get_me() - data = os.urandom(2 ** 17) - client.send_file( - me, data, - progress_callback=lambda c, t: - print('test_cdn_download:uploading {:.2%}...'.format(c/t)) - ) - msg = client.get_messages(me)[1][0] - - out = BytesIO() - client.download_media(msg, out) - self.assertEqual(sha256(data).digest(), sha256(out.getvalue()).digest()) - - out = BytesIO() - client.download_media(msg, out) # Won't redirect - self.assertEqual(sha256(data).digest(), sha256(out.getvalue()).digest()) - - client.log_out() - finally: - client.disconnect() diff --git a/telethon_tests/test_network.py b/telethon_tests/test_network.py deleted file mode 100644 index 031ad99d..00000000 --- a/telethon_tests/test_network.py +++ /dev/null @@ -1,44 +0,0 @@ -import random -import socket -import threading -import unittest - -import telethon.network.authenticator as authenticator -from telethon.extensions import TcpClient -from telethon.network import Connection - - -def run_server_echo_thread(port): - def server_thread(): - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', port)) - s.listen(1) - connection, address = s.accept() - with connection: - data = connection.recv(16) - connection.send(data) - - server = threading.Thread(target=server_thread) - server.start() - - -class NetworkTests(unittest.TestCase): - - @unittest.skip("test_tcp_client needs fix") - def test_tcp_client(self): - port = random.randint(50000, 60000) # Arbitrary non-privileged port - run_server_echo_thread(port) - - msg = b'Unit testing...' - client = TcpClient() - client.connect('localhost', port) - client.write(msg) - self.assertEqual(msg, client.read(15), - msg='Read message does not equal sent message') - client.close() - - @unittest.skip("Some parameters changed, so IP doesn't go there anymore.") - def test_authenticator(self): - transport = Connection('149.154.167.91', 443) - self.assertTrue(authenticator.do_authentication(transport)) - transport.close() diff --git a/telethon_tests/test_parser.py b/telethon_tests/test_parser.py deleted file mode 100644 index c87686a6..00000000 --- a/telethon_tests/test_parser.py +++ /dev/null @@ -1,8 +0,0 @@ -import unittest - - -class ParserTests(unittest.TestCase): - """There are no tests yet""" - @unittest.skip("there should be parser tests") - def test_parser(self): - self.assertTrue(True) diff --git a/telethon_tests/test_tl.py b/telethon_tests/test_tl.py deleted file mode 100644 index 189259f5..00000000 --- a/telethon_tests/test_tl.py +++ /dev/null @@ -1,8 +0,0 @@ -import unittest - - -class TLTests(unittest.TestCase): - """There are no tests yet""" - @unittest.skip("there should be TL tests") - def test_tl(self): - self.assertTrue(True) \ No newline at end of file diff --git a/telethon_tests/test_utils.py b/telethon_tests/test_utils.py deleted file mode 100644 index 4a550e3d..00000000 --- a/telethon_tests/test_utils.py +++ /dev/null @@ -1,66 +0,0 @@ -import os -import unittest -from telethon.tl import TLObject -from telethon.extensions import BinaryReader - - -class UtilsTests(unittest.TestCase): - def test_binary_writer_reader(self): - # Test that we can read properly - data = b'\x01\x05\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ - b'\x88A\x00\x00\x00\x00\x00\x009@\x1a\x1b\x1c\x1d\x1e\x1f ' \ - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ - b'\x00\x80' - - with BinaryReader(data) as reader: - value = reader.read_byte() - self.assertEqual(value, 1, - msg='Example byte should be 1 but is {}'.format(value)) - - value = reader.read_int() - self.assertEqual(value, 5, - msg='Example integer should be 5 but is {}'.format(value)) - - value = reader.read_long() - self.assertEqual(value, 13, - msg='Example long integer should be 13 but is {}'.format(value)) - - value = reader.read_float() - self.assertEqual(value, 17.0, - msg='Example float should be 17.0 but is {}'.format(value)) - - value = reader.read_double() - self.assertEqual(value, 25.0, - msg='Example double should be 25.0 but is {}'.format(value)) - - value = reader.read(7) - self.assertEqual(value, bytes([26, 27, 28, 29, 30, 31, 32]), - msg='Example bytes should be {} but is {}' - .format(bytes([26, 27, 28, 29, 30, 31, 32]), value)) - - value = reader.read_large_int(128, signed=False) - self.assertEqual(value, 2**127, - msg='Example large integer should be {} but is {}'.format(2**127, value)) - - def test_binary_tgwriter_tgreader(self): - small_data = os.urandom(33) - small_data_padded = os.urandom(19) # +1 byte for length = 20 (%4 = 0) - - large_data = os.urandom(999) - large_data_padded = os.urandom(1024) - - data = (small_data, small_data_padded, large_data, large_data_padded) - string = 'Testing Telegram strings, this should work properly!' - serialized = b''.join(TLObject.serialize_bytes(d) for d in data) + \ - TLObject.serialize_bytes(string) - - with BinaryReader(serialized) as reader: - # And then try reading it without errors (it should be unharmed!) - for datum in data: - value = reader.tgread_bytes() - self.assertEqual(value, datum, - msg='Example bytes should be {} but is {}'.format(datum, value)) - - value = reader.tgread_string() - self.assertEqual(value, string, - msg='Example string should be {} but is {}'.format(string, value)) diff --git a/try_telethon.py b/try_telethon.py deleted file mode 100755 index e0bf2c8a..00000000 --- a/try_telethon.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -import asyncio -import traceback - -from telethon_examples.interactive_telegram_client \ - import InteractiveTelegramClient - - -def load_settings(path='api/settings'): - """Loads the user settings located under `api/`""" - result = {} - with open(path, 'r', encoding='utf-8') as file: - for line in file: - value_pair = line.split('=') - left = value_pair[0].strip() - right = value_pair[1].strip() - if right.isnumeric(): - result[left] = int(right) - else: - result[left] = right - - return result - - -if __name__ == '__main__': - # Load the settings and initialize the client - settings = load_settings() - kwargs = {} - if settings.get('socks_proxy'): - import socks # $ pip install pysocks - host, port = settings['socks_proxy'].split(':') - kwargs = dict(proxy=(socks.SOCKS5, host, int(port))) - - client = InteractiveTelegramClient( - session_user_id=str(settings.get('session_name', 'anonymous')), - user_phone=str(settings['user_phone']), - api_id=settings['api_id'], - api_hash=str(settings['api_hash']), - **kwargs) - - print('Initialization done!') - loop = asyncio.get_event_loop() - - try: - loop.run_until_complete(client.run()) - - except Exception as e: - print('Unexpected error ({}): {} at\n{}'.format( - type(e), e, traceback.format_exc())) - - finally: - loop.run_until_complete(client.disconnect()) - print('Thanks for trying the interactive example! Exiting...')