From b346561f898b79712b8cfe7017e6ce33787f72e4 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 13:24:32 +0100 Subject: [PATCH 01/22] Remove unnecessary call to .get_input_entity() --- telethon/telegram_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index 5a5d99ec..a5ce4572 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -504,7 +504,7 @@ class TelegramClient(TelegramBareClient): if limit == 0: # No messages, but we still need to know the total message count result = self(GetHistoryRequest( - peer=self.get_input_entity(entity), limit=1, + peer=entity, limit=1, offset_date=None, offset_id=0, max_id=0, min_id=0, add_offset=0 )) return getattr(result, 'count', len(result.messages)), [], [] From 778c844a649e050b472d547c3b7ad1ce72c5ecc0 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 13:25:13 +0100 Subject: [PATCH 02/22] Use logger.exception instead logger.error on ReadThread --- telethon/telegram_bare_client.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 67ba0c88..87661e94 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -843,9 +843,8 @@ class TelegramBareClient: self.idle(stop_signals=tuple()) except Exception as error: # Unknown exception, pass it to the main thread - self._logger.error( - 'Unknown error on the read thread, please report', - error + self._logger.exception( + 'Unknown error on the read thread, please report' ) try: From 959e824c1c0be549b92d0ca9a2a87c3abf62f88b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 13:26:49 +0100 Subject: [PATCH 03/22] Reduce indent level to simplify flow on __call__ --- telethon/telegram_bare_client.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index 87661e94..a9e2fb30 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -423,21 +423,21 @@ class TelegramBareClient: result = self._invoke( sender, call_receive, update_state, *requests ) - if result is None: - sleep(1) - self._logger.debug('RPC failed. Attempting reconnection.') - # The ReadThread has priority when attempting reconnection, - # since this thread is constantly running while __call__ is - # only done sometimes. Here try connecting only once/retry. - if sender == self._sender: - if not self._reconnect_lock.locked(): - with self._reconnect_lock: - self._reconnect() - else: - sender.connect() - else: + if result is not None: return result + self._logger.debug('RPC failed. Attempting reconnection.') + sleep(1) + # The ReadThread has priority when attempting reconnection, + # since this thread is constantly running while __call__ is + # only done sometimes. Here try connecting only once/retry. + if sender == self._sender: + if not self._reconnect_lock.locked(): + with self._reconnect_lock: + self._reconnect() + else: + sender.connect() + raise ValueError('Number of retries reached 0.') finally: if sender != self._sender: From ee5915e86db20edbe65c6744d44d122b46b1a954 Mon Sep 17 00:00:00 2001 From: Vladislav Kolesnichenko Date: Thu, 16 Nov 2017 15:30:18 +0300 Subject: [PATCH 04/22] Add support for connecting through IPv6 (#425 for #112) --- telethon/telegram_bare_client.py | 19 ++++++++++++++++--- telethon/telegram_client.py | 2 ++ telethon/tl/session.py | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index a9e2fb30..cd01f009 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -39,6 +39,11 @@ from .update_state import UpdateState from .utils import get_appropriated_part_size +DEFAULT_IPV4_IP = '149.154.167.51' +DEFAULT_IPV6_IP = '[2001:67c:4e8:f002::a]' +DEFAULT_PORT = 443 + + class TelegramBareClient: """Bare Telegram Client with just the minimum - @@ -69,6 +74,7 @@ class TelegramBareClient: def __init__(self, session, api_id, api_hash, connection_mode=ConnectionMode.TCP_FULL, + use_ipv6=False, proxy=None, update_workers=None, spawn_read_thread=False, @@ -80,6 +86,8 @@ class TelegramBareClient: "Your API ID or Hash cannot be empty or None. " "Refer to Telethon's README.rst for more information.") + self._use_ipv6 = use_ipv6 + # Determine what session object we have if isinstance(session, str) or session is None: session = Session.try_load_or_create_new(session) @@ -88,6 +96,11 @@ class TelegramBareClient: 'The given session must be a str or a Session instance.' ) + if not session.server_address: + session.port = DEFAULT_PORT + session.server_address = \ + DEFAULT_IPV6_IP if self._use_ipv6 else DEFAULT_IPV4_IP + self.session = session self.api_id = int(api_id) self.api_hash = api_hash @@ -282,7 +295,7 @@ class TelegramBareClient: return self._recv_thread is not None and \ threading.get_ident() == self._recv_thread.ident - def _get_dc(self, dc_id, ipv6=False, cdn=False): + def _get_dc(self, dc_id, cdn=False): """Gets the Data Center (DC) associated to 'dc_id'""" if not TelegramBareClient._config: TelegramBareClient._config = self(GetConfigRequest()) @@ -295,7 +308,7 @@ class TelegramBareClient: return next( dc for dc in TelegramBareClient._config.dc_options - if dc.id == dc_id and bool(dc.ipv6) == ipv6 and bool(dc.cdn) == cdn + if dc.id == dc_id and bool(dc.ipv6) == self._use_ipv6 and bool(dc.cdn) == cdn ) except StopIteration: if not cdn: @@ -303,7 +316,7 @@ class TelegramBareClient: # New configuration, perhaps a new CDN was added? TelegramBareClient._config = self(GetConfigRequest()) - return self._get_dc(dc_id, ipv6=ipv6, cdn=cdn) + return self._get_dc(dc_id, cdn=cdn) def _get_exported_client(self, dc_id): """Creates and connects a new TelegramBareClient for the desired DC. diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index a5ce4572..ae864ea6 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -69,6 +69,7 @@ class TelegramClient(TelegramBareClient): def __init__(self, session, api_id, api_hash, connection_mode=ConnectionMode.TCP_FULL, + use_ipv6=False, proxy=None, update_workers=None, timeout=timedelta(seconds=5), @@ -113,6 +114,7 @@ class TelegramClient(TelegramBareClient): super().__init__( session, api_id, api_hash, connection_mode=connection_mode, + use_ipv6=use_ipv6, proxy=proxy, update_workers=update_workers, spawn_read_thread=spawn_read_thread, diff --git a/telethon/tl/session.py b/telethon/tl/session.py index f597048f..bbdcc590 100644 --- a/telethon/tl/session.py +++ b/telethon/tl/session.py @@ -64,8 +64,8 @@ class Session: self._last_msg_id = 0 # Long # These values will be saved - self.server_address = '91.108.56.165' - self.port = 443 + self.server_address = None + self.port = None self.auth_key = None self.layer = 0 self.salt = 0 # Unsigned long From edd73ed69aca8703e76bd808001bcfaa019cc30f Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 13:40:25 +0100 Subject: [PATCH 05/22] Allow switching from IPv4 to IPv6 and vice versa --- telethon/telegram_bare_client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/telethon/telegram_bare_client.py b/telethon/telegram_bare_client.py index cd01f009..97251547 100644 --- a/telethon/telegram_bare_client.py +++ b/telethon/telegram_bare_client.py @@ -96,7 +96,9 @@ class TelegramBareClient: 'The given session must be a str or a Session instance.' ) - if not session.server_address: + # ':' in session.server_address is True if it's an IPv6 address + if (not session.server_address or + (':' in session.server_address) != use_ipv6): session.port = DEFAULT_PORT session.server_address = \ DEFAULT_IPV6_IP if self._use_ipv6 else DEFAULT_IPV4_IP From 4ddbc78699700796ba9013cd2841461e0f28883b Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 13:47:15 +0100 Subject: [PATCH 06/22] Ensure IPv6 addresses are surrounded by '[]' (#425) --- telethon/extensions/tcp_client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/telethon/extensions/tcp_client.py b/telethon/extensions/tcp_client.py index 164429f3..af9bfbfe 100644 --- a/telethon/extensions/tcp_client.py +++ b/telethon/extensions/tcp_client.py @@ -37,6 +37,12 @@ class TcpClient: 'timeout' must be given in seconds """ if ':' in ip: # IPv6 + # The address needs to be surrounded by [] as discussed on PR#425 + if not ip.startswith('['): + ip = '[' + ip + if not ip.endswith(']'): + ip = ip + ']' + mode, address = socket.AF_INET6, (ip, port, 0, 0) else: mode, address = socket.AF_INET, (ip, port) From e5deaf5db8428c45f727a391f4f4acff2b61cb55 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 19:07:53 +0100 Subject: [PATCH 07/22] Fix c4e07cf, md parsing adding unfinished entity at wrong offset --- telethon/extensions/markdown.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/telethon/extensions/markdown.py b/telethon/extensions/markdown.py index 3cdf95f7..dbbdb5fe 100644 --- a/telethon/extensions/markdown.py +++ b/telethon/extensions/markdown.py @@ -117,7 +117,10 @@ def parse(message, delimiters=None, url_re=None): # We may have found some a delimiter but not its ending pair. # If this is the case, we want to insert the delimiter character back. if current is not None: - message = \ - message[:current.offset] + end_delimiter + message[current.offset:] + message = ( + message[:2 * current.offset] + + end_delimiter + + message[2 * current.offset:] + ) return message.decode('utf-16le'), result From 346c5bb3030d4dfeb0dde3e26cc90373e3f0b996 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 19:13:13 +0100 Subject: [PATCH 08/22] Add method to md parser to extract text surrounded by entities --- telethon/extensions/markdown.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/telethon/extensions/markdown.py b/telethon/extensions/markdown.py index dbbdb5fe..a11b1aab 100644 --- a/telethon/extensions/markdown.py +++ b/telethon/extensions/markdown.py @@ -4,6 +4,9 @@ for use within the library, which attempts to handle emojies correctly, since they seem to count as two characters and it's a bit strange. """ import re + +from telethon.tl import TLObject + from ..tl.types import ( MessageEntityBold, MessageEntityItalic, MessageEntityCode, MessageEntityPre, MessageEntityTextUrl @@ -124,3 +127,23 @@ def parse(message, delimiters=None, url_re=None): ) return message.decode('utf-16le'), result + + +def get_inner_text(text, entity): + """Gets the inner text that's surrounded by the given entity or entities. + For instance: text = 'hey!', entity = MessageEntityBold(2, 2) -> 'y!'. + """ + if not isinstance(entity, TLObject) and hasattr(entity, '__iter__'): + multiple = True + else: + entity = [entity] + multiple = False + + text = text.encode('utf-16le') + result = [] + for e in entity: + start = e.offset * 2 + end = (e.offset + e.length) * 2 + result.append(text[start:end].decode('utf-16le')) + + return result if multiple else result[0] From 01f55200f29f59bf2125ed768daa2f9d0c508d95 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 16 Nov 2017 19:18:26 +0100 Subject: [PATCH 09/22] Update to v0.15.5 --- telethon/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/version.py b/telethon/version.py index bafdcc72..096fbd6c 100644 --- a/telethon/version.py +++ b/telethon/version.py @@ -1,3 +1,3 @@ # Versions should comply with PEP440. # This line is parsed in setup.py: -__version__ = '0.15.4' +__version__ = '0.15.5' From 976777414756bfc0e14a056cdd160e56089ed1ae Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 17 Nov 2017 15:57:48 +0100 Subject: [PATCH 10/22] Fix import in markdown parser not being relative --- telethon/extensions/markdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telethon/extensions/markdown.py b/telethon/extensions/markdown.py index a11b1aab..f4f3f740 100644 --- a/telethon/extensions/markdown.py +++ b/telethon/extensions/markdown.py @@ -5,7 +5,7 @@ since they seem to count as two characters and it's a bit strange. """ import re -from telethon.tl import TLObject +from ..tl import TLObject from ..tl.types import ( MessageEntityBold, MessageEntityItalic, MessageEntityCode, From 231ecf3f2a4497d2d1a2cd423efcc1005a956b86 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 20 Nov 2017 00:56:05 +0800 Subject: [PATCH 11/22] add readthedocs files --- readthedocs/Makefile | 20 ++ readthedocs/conf.py | 174 ++++++++++++++++++ readthedocs/extra/examples-signing-in.rst | 54 ++++++ .../extra/examples-working-with-messages.rst | 99 ++++++++++ readthedocs/extra/examples.rst | 66 +++++++ readthedocs/extra/getting_started.rst | 103 +++++++++++ readthedocs/index.rst | 40 ++++ readthedocs/make.bat | 36 ++++ readthedocs/modules.rst | 7 + readthedocs/telethon.crypto.rst | 61 ++++++ readthedocs/telethon.errors.rst | 21 +++ readthedocs/telethon.extensions.rst | 29 +++ readthedocs/telethon.network.rst | 37 ++++ readthedocs/telethon.rst | 89 +++++++++ readthedocs/telethon.tl.custom.rst | 12 ++ readthedocs/telethon.tl.rst | 57 ++++++ 16 files changed, 905 insertions(+) create mode 100644 readthedocs/Makefile create mode 100644 readthedocs/conf.py create mode 100644 readthedocs/extra/examples-signing-in.rst create mode 100644 readthedocs/extra/examples-working-with-messages.rst create mode 100644 readthedocs/extra/examples.rst create mode 100644 readthedocs/extra/getting_started.rst create mode 100644 readthedocs/index.rst create mode 100644 readthedocs/make.bat create mode 100644 readthedocs/modules.rst create mode 100644 readthedocs/telethon.crypto.rst create mode 100644 readthedocs/telethon.errors.rst create mode 100644 readthedocs/telethon.extensions.rst create mode 100644 readthedocs/telethon.network.rst create mode 100644 readthedocs/telethon.rst create mode 100644 readthedocs/telethon.tl.custom.rst create mode 100644 readthedocs/telethon.tl.rst diff --git a/readthedocs/Makefile b/readthedocs/Makefile new file mode 100644 index 00000000..fd6e0d0a --- /dev/null +++ b/readthedocs/Makefile @@ -0,0 +1,20 @@ +# 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 new file mode 100644 index 00000000..18ff1a17 --- /dev/null +++ b/readthedocs/conf.py @@ -0,0 +1,174 @@ +#!/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 os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- 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'] + +# 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. +version = '0.15' +# The full version, including alpha/beta/rc tags. +release = '0.15.5' + +# 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', + 'Jeff', '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/extra/examples-signing-in.rst b/readthedocs/extra/examples-signing-in.rst new file mode 100644 index 00000000..cade3649 --- /dev/null +++ b/readthedocs/extra/examples-signing-in.rst @@ -0,0 +1,54 @@ +========================= +Signing In +========================= + +Two Factor Authorization (2FA) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you have Two Factor Authorization (from now on, 2FA) enabled on your account, calling +:meth:`telethon.TelegramClient.sign_in` will raise a `SessionPasswordNeededError`. +When this happens, just :meth:`telethon.TelegramClient.sign_in` 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()) + +Enabling 2FA +************* + +If you don't have 2FA enabled, but you would like to do so through Telethon, take as example the following code snippet: + + .. code-block:: python + + import os + from hashlib import sha256 + from telethon.tl.functions import account + from telethon.tl.types.account import PasswordInputSettings + + new_salt = client(account.GetPasswordRequest()).new_salt + salt = new_salt + os.urandom(8) # new random salt + + pw = 'secret'.encode('utf-8') # type your new password here + hint = 'hint' + + pw_salted = salt + pw + salt + pw_hash = sha256(pw_salted).digest() + + result = client(account.UpdatePasswordSettingsRequest( + current_password_hash=salt, + new_settings=PasswordInputSettings( + new_salt=salt, + new_password_hash=pw_hash, + hint=hint + ) + )) + +Thanks to `Issue 259 `_ for the tip! + diff --git a/readthedocs/extra/examples-working-with-messages.rst b/readthedocs/extra/examples-working-with-messages.rst new file mode 100644 index 00000000..1c47d328 --- /dev/null +++ b/readthedocs/extra/examples-working-with-messages.rst @@ -0,0 +1,99 @@ +========================= +Working with messages +========================= + +Forwarding messages +******************* + +Note that ForwardMessageRequest_ (note it's Message, singular) will *not* work if channels are involved. +This is because channel (and megagroups) IDs are not unique, so you also need to know who the sender is +(a parameter this request doesn't have). + +Either way, you are encouraged to use ForwardMessagesRequest_ (note it's Message*s*, plural) *always*, +since it is more powerful, as follows: + + .. code-block:: python + + from telethon.tl.functions.messages import ForwardMessagesRequest + # note the s ^ + + 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 +******************* + +Messages are searched through the obvious SearchRequest_, but you may run into issues_. A valid example would be: + + .. code-block:: python + + result = client(SearchRequest( + entity, 'query', InputMessagesFilterEmpty(), None, None, 0, 0, 100 + )) + +It's important to note that the optional parameter ``from_id`` has been left omitted and thus defaults to ``None``. +Changing it to 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 ``InputUser`` or ``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 + sticker_sets = client(GetAllStickersRequest(0)) + + # Choose a sticker set + 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(SendMediaRequest( + peer=client.get_me(), + media=InputMediaDocument( + id=InputDocument( + id=stickers.documents[0].id, + access_hash=stickers.documents[0].access_hash + ), + caption='' + ) + )) + + +.. _ForwardMessageRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_message.html +.. _ForwardMessagesRequest: https://lonamiwebs.github.io/Telethon/methods/messages/forward_messages.html +.. _SearchRequest: https://lonamiwebs.github.io/Telethon/methods/messages/search.html +.. _issues: https://github.com/LonamiWebs/Telethon/issues/215 +.. _InputUserEmpty: https://lonamiwebs.github.io/Telethon/constructors/input_user_empty.html diff --git a/readthedocs/extra/examples.rst b/readthedocs/extra/examples.rst new file mode 100644 index 00000000..c068f2fc --- /dev/null +++ b/readthedocs/extra/examples.rst @@ -0,0 +1,66 @@ + + +***************** +Examples +***************** + +Prelude +--------- + +Before reading any specific example, make sure to read the following common steps: + +All the examples assume that you have successfully created a client and you're authorized as follows: + + .. code-block:: python + + from telethon import TelegramClient + + # Use your own values here + api_id = 12345 + api_hash = '0123456789abcdef0123456789abcdef' + phone_number = '+34600000000' + + client = TelegramClient('some_name', api_id, api_hash) + client.connect() # Must return True, otherwise, try again + + if not client.is_user_authorized(): + client.send_code_request(phone_number) + # .sign_in() may raise PhoneNumberUnoccupiedError + # In that case, you need to call .sign_up() to get a new account + client.sign_in(phone_number, input('Enter code: ')) + + # The `client´ is now ready + +Although Python will probably clean up the resources used by the ``TelegramClient``, +you should always ``.disconnect()`` it once you're done: + + .. code-block:: python + + try: + # Code using the client goes here + except: + # No matter what happens, always disconnect in the end + client.disconnect() + +If the examples aren't enough, you're strongly advised to read the source code +for the InteractiveTelegramClient_ for an overview on how you could build your next script. +This example shows a basic usage more than enough in most cases. Even reading the source +for the TelegramClient_ may help a lot! + + +Signing In +-------------- + +.. toctree:: + examples-signing-in + + +Working with messages +----------------------- + +.. toctree:: + examples-working-with-messages + + +.. _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/getting_started.rst b/readthedocs/extra/getting_started.rst new file mode 100644 index 00000000..3a6b36a8 --- /dev/null +++ b/readthedocs/extra/getting_started.rst @@ -0,0 +1,103 @@ +.. 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. + + +================= +Getting Started! +================= + +Installation +************** + +To install Telethon, simply do: + + ``pip install telethon`` + +If you get something like ``"SyntaxError: invalid syntax"`` or any other error while installing, it's probably because ``pip`` defaults to Python 2, which is not supported. Use ``pip3`` instead. + +If you already have the library installed, upgrade with: + + ``pip install --upgrade telethon``. + +You can also install the library directly from GitHub or a fork: + + .. code-block:: python + + # pip 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. + + + + +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 be changed later as far as I'm aware. + +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 + + # 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' + phone = '+34600000000' + + client = TelegramClient('session_name', api_id, api_hash) + client.connect() + + # If you already have a previous 'session_name.session' file, skip this. + client.sign_in(phone=phone) + me = client.sign_in(code=77777) # Put whatever code you received here. + +**More details**: `Click here `_ + + +Simple Stuff +************** + .. code-block:: python + + print(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) + total, messages, senders = client.get_message_history('username') + client.download_media(messages[0]) + + +Diving In +************** + +.. note:: More info in our Wiki! + +Sending Requests +^^^^^^^^^^^^^^^^^^^^^ + `Here `__ + +Working with updates +^^^^^^^^^^^^^^^^^^^^^ + `Here `__ + +Accessing the full API +^^^^^^^^^^^^^^^^^^^^^^^ + `Here `__ + diff --git a/readthedocs/index.rst b/readthedocs/index.rst new file mode 100644 index 00000000..8f036adc --- /dev/null +++ b/readthedocs/index.rst @@ -0,0 +1,40 @@ +.. 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 `_. + + + +**************** +Getting Started +**************** +.. toctree:: + extra/getting_started + + +*************** +Examples +*************** +.. toctree:: + extra/examples + + +*************** +Modules +*************** +.. toctree:: + telethon + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/readthedocs/make.bat b/readthedocs/make.bat new file mode 100644 index 00000000..f51f7234 --- /dev/null +++ b/readthedocs/make.bat @@ -0,0 +1,36 @@ +@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 new file mode 100644 index 00000000..f710574a --- /dev/null +++ b/readthedocs/modules.rst @@ -0,0 +1,7 @@ +telethon +======== + +.. toctree:: + :maxdepth: 3 + + telethon diff --git a/readthedocs/telethon.crypto.rst b/readthedocs/telethon.crypto.rst new file mode 100644 index 00000000..3c11416d --- /dev/null +++ b/readthedocs/telethon.crypto.rst @@ -0,0 +1,61 @@ +telethon\.crypto package +======================== + + +telethon\.crypto\.aes module +---------------------------- + +.. automodule:: telethon.crypto.aes + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.aes\_ctr module +--------------------------------- + +.. automodule:: telethon.crypto.aes_ctr + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.auth\_key module +---------------------------------- + +.. automodule:: telethon.crypto.auth_key + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.cdn\_decrypter module +--------------------------------------- + +.. automodule:: telethon.crypto.cdn_decrypter + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.factorization module +-------------------------------------- + +.. automodule:: telethon.crypto.factorization + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.libssl module +------------------------------- + +.. automodule:: telethon.crypto.libssl + :members: + :undoc-members: + :show-inheritance: + +telethon\.crypto\.rsa module +---------------------------- + +.. automodule:: telethon.crypto.rsa + :members: + :undoc-members: + :show-inheritance: + + diff --git a/readthedocs/telethon.errors.rst b/readthedocs/telethon.errors.rst new file mode 100644 index 00000000..2e94fe33 --- /dev/null +++ b/readthedocs/telethon.errors.rst @@ -0,0 +1,21 @@ +telethon\.errors package +======================== + + +telethon\.errors\.common module +------------------------------- + +.. automodule:: telethon.errors.common + :members: + :undoc-members: + :show-inheritance: + +telethon\.errors\.rpc\_base\_errors module +------------------------------------------ + +.. automodule:: telethon.errors.rpc_base_errors + :members: + :undoc-members: + :show-inheritance: + + diff --git a/readthedocs/telethon.extensions.rst b/readthedocs/telethon.extensions.rst new file mode 100644 index 00000000..578728b5 --- /dev/null +++ b/readthedocs/telethon.extensions.rst @@ -0,0 +1,29 @@ +telethon\.extensions package +============================ + + +telethon\.extensions\.binary\_reader module +------------------------------------------- + +.. automodule:: telethon.extensions.binary_reader + :members: + :undoc-members: + :show-inheritance: + +telethon\.extensions\.markdown module +------------------------------------- + +.. automodule:: telethon.extensions.markdown + :members: + :undoc-members: + :show-inheritance: + +telethon\.extensions\.tcp\_client module +---------------------------------------- + +.. automodule:: telethon.extensions.tcp_client + :members: + :undoc-members: + :show-inheritance: + + diff --git a/readthedocs/telethon.network.rst b/readthedocs/telethon.network.rst new file mode 100644 index 00000000..3600e985 --- /dev/null +++ b/readthedocs/telethon.network.rst @@ -0,0 +1,37 @@ +telethon\.network package +========================= + + +telethon\.network\.authenticator module +--------------------------------------- + +.. automodule:: telethon.network.authenticator + :members: + :undoc-members: + :show-inheritance: + +telethon\.network\.connection module +------------------------------------ + +.. automodule:: telethon.network.connection + :members: + :undoc-members: + :show-inheritance: + +telethon\.network\.mtproto\_plain\_sender module +------------------------------------------------ + +.. automodule:: telethon.network.mtproto_plain_sender + :members: + :undoc-members: + :show-inheritance: + +telethon\.network\.mtproto\_sender module +----------------------------------------- + +.. automodule:: telethon.network.mtproto_sender + :members: + :undoc-members: + :show-inheritance: + + diff --git a/readthedocs/telethon.rst b/readthedocs/telethon.rst new file mode 100644 index 00000000..2d3c269c --- /dev/null +++ b/readthedocs/telethon.rst @@ -0,0 +1,89 @@ +telethon package +================ + + +telethon\.helpers module +------------------------ + +.. automodule:: telethon.helpers + :members: + :undoc-members: + :show-inheritance: + +telethon\.telegram\_bare\_client module +--------------------------------------- + +.. automodule:: telethon.telegram_bare_client + :members: + :undoc-members: + :show-inheritance: + +telethon\.telegram\_client module +--------------------------------- + +.. automodule:: telethon.telegram_client + :members: + :undoc-members: + :show-inheritance: + +telethon\.update\_state module +------------------------------ + +.. automodule:: telethon.update_state + :members: + :undoc-members: + :show-inheritance: + +telethon\.utils module +---------------------- + +.. automodule:: telethon.utils + :members: + :undoc-members: + :show-inheritance: + + +telethon\.cryto package +------------------------ + +.. toctree:: + + telethon.crypto + +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 new file mode 100644 index 00000000..a1290869 --- /dev/null +++ b/readthedocs/telethon.tl.custom.rst @@ -0,0 +1,12 @@ +telethon\.tl\.custom package +============================ + + +telethon\.tl\.custom\.draft module +---------------------------------- + +.. automodule:: telethon.tl.custom.draft + :members: + :undoc-members: + :show-inheritance: + diff --git a/readthedocs/telethon.tl.rst b/readthedocs/telethon.tl.rst new file mode 100644 index 00000000..6fbb1f00 --- /dev/null +++ b/readthedocs/telethon.tl.rst @@ -0,0 +1,57 @@ +telethon\.tl package +==================== + + +.. toctree:: + + telethon.tl.custom + + +telethon\.tl\.entity\_database module +------------------------------------- + +.. automodule:: telethon.tl.entity_database + :members: + :undoc-members: + :show-inheritance: + +telethon\.tl\.gzip\_packed module +--------------------------------- + +.. automodule:: telethon.tl.gzip_packed + :members: + :undoc-members: + :show-inheritance: + +telethon\.tl\.message\_container module +--------------------------------------- + +.. automodule:: telethon.tl.message_container + :members: + :undoc-members: + :show-inheritance: + +telethon\.tl\.session module +---------------------------- + +.. automodule:: telethon.tl.session + :members: + :undoc-members: + :show-inheritance: + +telethon\.tl\.tl\_message module +-------------------------------- + +.. automodule:: telethon.tl.tl_message + :members: + :undoc-members: + :show-inheritance: + +telethon\.tl\.tlobject module +----------------------------- + +.. automodule:: telethon.tl.tlobject + :members: + :undoc-members: + :show-inheritance: + From 34e7ae026ec638875a7351cce3ad93f253c41280 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 20 Nov 2017 01:04:51 +0800 Subject: [PATCH 12/22] add requirements.txt for readthedocs --- readthedocs/requirements.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 readthedocs/requirements.txt diff --git a/readthedocs/requirements.txt b/readthedocs/requirements.txt new file mode 100644 index 00000000..97c7493d --- /dev/null +++ b/readthedocs/requirements.txt @@ -0,0 +1 @@ +telethon \ No newline at end of file From f6ec7e47a7802e003cb06f03e89bb53c9268ea5e Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 20 Nov 2017 12:12:31 +0800 Subject: [PATCH 13/22] add a bunch more readthedocs from wiki --- readthedocs/extra/advanced-usage/bots.rst | 59 ++++ .../signing-in.rst} | 4 + .../extra/advanced-usage/users-and-chats.rst | 323 ++++++++++++++++++ .../working-with-messages.rst} | 4 + .../extra/{examples.rst => advanced.rst} | 20 +- .../extra/basic/accessing-the-full-api.rst | 115 +++++++ readthedocs/extra/basic/creating-a-client.rst | 76 +++++ readthedocs/extra/basic/getting-started.rst | 54 +++ readthedocs/extra/basic/installation.rst | 82 +++++ readthedocs/extra/basic/sending-requests.rst | 55 +++ readthedocs/extra/basic/sessions.rst | 48 +++ .../extra/basic/working-with-updates.rst | 133 ++++++++ readthedocs/extra/getting_started.rst | 103 ------ ...eleted-limited-or-deactivated-accounts.rst | 26 ++ .../extra/troubleshooting/enable-logging.rst | 24 ++ .../extra/troubleshooting/rpc-errors.rst | 27 ++ readthedocs/index.rst | 47 ++- 17 files changed, 1067 insertions(+), 133 deletions(-) create mode 100644 readthedocs/extra/advanced-usage/bots.rst rename readthedocs/extra/{examples-signing-in.rst => advanced-usage/signing-in.rst} (95%) create mode 100644 readthedocs/extra/advanced-usage/users-and-chats.rst rename readthedocs/extra/{examples-working-with-messages.rst => advanced-usage/working-with-messages.rst} (98%) rename readthedocs/extra/{examples.rst => advanced.rst} (89%) create mode 100644 readthedocs/extra/basic/accessing-the-full-api.rst create mode 100644 readthedocs/extra/basic/creating-a-client.rst create mode 100644 readthedocs/extra/basic/getting-started.rst create mode 100644 readthedocs/extra/basic/installation.rst create mode 100644 readthedocs/extra/basic/sending-requests.rst create mode 100644 readthedocs/extra/basic/sessions.rst create mode 100644 readthedocs/extra/basic/working-with-updates.rst delete mode 100644 readthedocs/extra/getting_started.rst create mode 100644 readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst create mode 100644 readthedocs/extra/troubleshooting/enable-logging.rst create mode 100644 readthedocs/extra/troubleshooting/rpc-errors.rst diff --git a/readthedocs/extra/advanced-usage/bots.rst b/readthedocs/extra/advanced-usage/bots.rst new file mode 100644 index 00000000..091eada1 --- /dev/null +++ b/readthedocs/extra/advanced-usage/bots.rst @@ -0,0 +1,59 @@ +====== +Bots +====== + +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 `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 +`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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To interact with a message that has a special reply markup, such as +`@VoteBot`__ polls, you would use +`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://lonamiwebs.github.io/Telethon/methods/messages/get_inline_bot_results.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/send_inline_bot_result.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/get_bot_callback_answer.html +__ https://t.me/vote \ No newline at end of file diff --git a/readthedocs/extra/examples-signing-in.rst b/readthedocs/extra/advanced-usage/signing-in.rst similarity index 95% rename from readthedocs/extra/examples-signing-in.rst rename to readthedocs/extra/advanced-usage/signing-in.rst index cade3649..08f4fe3d 100644 --- a/readthedocs/extra/examples-signing-in.rst +++ b/readthedocs/extra/advanced-usage/signing-in.rst @@ -2,6 +2,10 @@ Signing In ========================= +.. note:: + Make sure you have gone through :ref:`prelude` already! + + Two Factor Authorization (2FA) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/readthedocs/extra/advanced-usage/users-and-chats.rst b/readthedocs/extra/advanced-usage/users-and-chats.rst new file mode 100644 index 00000000..3f3e0729 --- /dev/null +++ b/readthedocs/extra/advanced-usage/users-and-chats.rst @@ -0,0 +1,323 @@ +========================= +Users and Chats +========================= + +.. note:: + Make sure you have gone through :ref:`prelude` already! + +.. contents:: + +.. _retrieving-an-entity: + +Retrieving an entity (user or group) +************************************** +An “entity” is used to refer to either an `User`__ or a `Chat`__ +(which includes a `Channel`__). The most straightforward way to get +an entity is to use ``TelegramClient.get_entity()``. This method accepts +either a string, which can be a username, phone number or `t.me`__-like +link, or an integer that will be the ID of an **user**. You can use it +like so: + + .. code-block:: python + + # all of these work + 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) + +For the last one to work, the library must have “seen” the user at least +once. The library will “see” the user as long as any request contains +them, so if you’ve called ``.get_dialogs()`` for instance, and your +friend was there, the library will know about them. For more, read about +the :ref:`sessions`. + +If you want to get a channel or chat by ID, you need to specify that +they are a channel or a chat. The library can’t infer what they are by +just their ID (unless the ID is marked, but this is only done +internally), so you need to wrap the ID around a `Peer`__ object: + + .. code-block:: python + + 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)) + +**Note** that most requests don’t ask for an ``User``, or a ``Chat``, +but rather for ``InputUser``, ``InputChat``, and so on. If this is the +case, you should prefer ``.get_input_entity()`` over ``.get_entity()``, +as it will be immediate if you provide an ID (whereas ``.get_entity()`` +may need to find who the entity is first). + +Via your open “chats” (dialogs) +------------------------------- + +.. note:: + Please read here: :ref:`retrieving-all-dialogs`. + +Via ResolveUsernameRequest +-------------------------- + +This is the request used by ``.get_entity`` internally, but you can also +use it by hand: + +.. code-block:: python + + from telethon.tl.functions.contacts import ResolveUsernameRequest + + result = client(ResolveUsernameRequest('username')) + found_chats = result.chats + found_users = result.users + # result.peer may be a PeerUser, PeerChat or PeerChannel + +See `Peer`__ for more information about this result. + +Via MessageFwdHeader +-------------------- + +If all you have is a `MessageFwdHeader`__ after you retrieved a bunch +of messages, this gives you access to the ``from_id`` (if forwarded from +an user) and ``channel_id`` (if forwarded from a channel). Invoking +`GetMessagesRequest`__ also returns a list of ``chats`` and +``users``, and you can find the desired entity there: + + .. code-block:: python + + # Logic to retrieve messages with `GetMessagesRequest´ + messages = foo() + fwd_header = bar() + + user = next(u for u in messages.users if u.id == fwd_header.from_id) + channel = next(c for c in messages.chats if c.id == fwd_header.channel_id) + +Or you can just call ``.get_entity()`` with the ID, as you should have +seen that user or channel before. A call to ``GetMessagesRequest`` may +still be neeed. + +Via GetContactsRequest +---------------------- + +The library will call this for you if you pass a phone number to +``.get_entity``, but again, it can be done manually. If the user you +want to talk to is a contact, you can use `GetContactsRequest`__: + + .. code-block:: python + + from telethon.tl.functions.contacts import GetContactsRequest + from telethon.tl.types.contacts import Contacts + + contacts = client(GetContactsRequest(0)) + if isinstance(contacts, Contacts): + users = contacts.users + contacts = contacts.contacts + +__ https://lonamiwebs.github.io/Telethon/types/user.html +__ https://lonamiwebs.github.io/Telethon/types/chat.html +__ https://lonamiwebs.github.io/Telethon/constructors/channel.html +__ https://t.me +__ https://lonamiwebs.github.io/Telethon/types/peer.html +__ https://lonamiwebs.github.io/Telethon/types/peer.html +__ https://lonamiwebs.github.io/Telethon/constructors/message_fwd_header.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/get_messages.html +__ https://lonamiwebs.github.io/Telethon/methods/contacts/get_contacts.html + + +.. _retrieving-all-dialogs: + +Retrieving all dialogs +*********************** + +There are several ``offset_xyz=`` parameters that have no effect at all, +but there's not much one can do since this is something the server should handle. +Currently, the only way to get all dialogs +(open chats, conversations, etc.) is by using the ``offset_date``: + + .. code-block:: python + + from telethon.tl.functions.messages import GetDialogsRequest + from telethon.tl.types import InputPeerEmpty + from time import sleep + + dialogs = [] + users = [] + chats = [] + + last_date = None + chunk_size = 20 + while True: + result = client(GetDialogsRequest( + offset_date=last_date, + offset_id=0, + offset_peer=InputPeerEmpty(), + limit=chunk_size + )) + dialogs.extend(result.dialogs) + users.extend(result.users) + chats.extend(result.chats) + if not result.messages: + break + last_date = min(msg.date for msg in result.messages) + sleep(2) + + +Joining a chat or channel +******************************* + +Note that `Chat`__\ s are normal groups, and `Channel`__\ s are a +special form of `Chat`__\ s, +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 `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`__. + +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 +`ImportChatInviteRequest`__ as follows: + + .. -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 `AddChatUserRequest`__, which +use is very straightforward: + + .. code-block:: python + + from telethon.tl.functions.messages import AddChatUserRequest + + client(AddChatUserRequest( + chat_id, + user_to_add, + fwd_limit=10 # allow the user to see the 10 last messages + )) + +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 `CheckChatInviteRequest`__, which takes in +the `hash`__ of said channel or group. + +__ https://lonamiwebs.github.io/Telethon/constructors/chat.html +__ https://lonamiwebs.github.io/Telethon/constructors/channel.html +__ https://lonamiwebs.github.io/Telethon/types/chat.html +__ https://lonamiwebs.github.io/Telethon/methods/channels/join_channel.html +__ https://lonamiwebs.github.io/Telethon/methods/channels/index.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/import_chat_invite.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/add_chat_user.html +__ https://lonamiwebs.github.io/Telethon/methods/messages/check_chat_invite.html +__ https://github.com/LonamiWebs/Telethon/wiki/Joining-a-chat-or-channel#joining-a-private-chat-or-channel + + +Retrieving all chat members (channels too) +****************************************** + +In order to get all the members from a mega-group or channel, you need +to use `GetParticipantsRequest`__. As we can see it needs an +`InputChannel`__, (passing the mega-group or channel you’re going to +use will work), and a mandatory `ChannelParticipantsFilter`__. The +closest thing to “no filter” is to simply use +`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.invoke(GetParticipantsRequest( + channel, ChannelParticipantsSearch(''), offset, limit + )) + if not participants.users: + break + all_participants.extend(participants.users) + offset += len(participants.users) + # sleep(1) # This line seems to be optional, no guarantees! + +Note that ``GetParticipantsRequest`` returns `ChannelParticipants`__, +which may have more information you need (like the role of the +participants, total count of members, etc.) + +__ https://lonamiwebs.github.io/Telethon/methods/channels/get_participants.html +__ https://lonamiwebs.github.io/Telethon/methods/channels/get_participants.html +__ https://lonamiwebs.github.io/Telethon/types/channel_participants_filter.html +__ https://lonamiwebs.github.io/Telethon/constructors/channel_participants_search.html +__ https://lonamiwebs.github.io/Telethon/constructors/channels/channel_participants.html + + +Recent Actions +******************** + +“Recent actions” is simply the name official applications have given to +the “admin log”. Simply use `GetAdminLogRequest`__ for that, and +you’ll get AdminLogResults.events in return which in turn has the final +`.action`__. + +__ https://lonamiwebs.github.io/Telethon/methods/channels/get_admin_log.html +__ https://lonamiwebs.github.io/Telethon/types/channel_admin_log_event_action.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 `GetMessagesViewsRequest`__, setting ``increment=True``: + + .. code-block:: python + + + # Obtain `channel' through dialogs or through client.get_entity() or anyhow. + # Obtain `msg_ids' through `.get_message_history()` or anyhow. Must be a list. + + client(GetMessagesViewsRequest( + peer=channel, + id=msg_ids, + increment=True + )) + +__ 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 +__ https://lonamiwebs.github.io/Telethon/methods/messages/get_messages_views.html \ No newline at end of file diff --git a/readthedocs/extra/examples-working-with-messages.rst b/readthedocs/extra/advanced-usage/working-with-messages.rst similarity index 98% rename from readthedocs/extra/examples-working-with-messages.rst rename to readthedocs/extra/advanced-usage/working-with-messages.rst index 1c47d328..2c141406 100644 --- a/readthedocs/extra/examples-working-with-messages.rst +++ b/readthedocs/extra/advanced-usage/working-with-messages.rst @@ -2,6 +2,10 @@ Working with messages ========================= +.. note:: + Make sure you have gone through :ref:`prelude` already! + + Forwarding messages ******************* diff --git a/readthedocs/extra/examples.rst b/readthedocs/extra/advanced.rst similarity index 89% rename from readthedocs/extra/examples.rst rename to readthedocs/extra/advanced.rst index c068f2fc..4433116d 100644 --- a/readthedocs/extra/examples.rst +++ b/readthedocs/extra/advanced.rst @@ -1,8 +1,4 @@ - - -***************** -Examples -***************** +.. _prelude: Prelude --------- @@ -48,19 +44,5 @@ This example shows a basic usage more than enough in most cases. Even reading th for the TelegramClient_ may help a lot! -Signing In --------------- - -.. toctree:: - examples-signing-in - - -Working with messages ------------------------ - -.. toctree:: - examples-working-with-messages - - .. _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/accessing-the-full-api.rst b/readthedocs/extra/basic/accessing-the-full-api.rst new file mode 100644 index 00000000..ceeea7d8 --- /dev/null +++ b/readthedocs/extra/basic/accessing-the-full-api.rst @@ -0,0 +1,115 @@ +========================== +Accessing the Full API +========================== + +The ``TelegramClient`` doesn’t offer a method for every single request +the Telegram API supports. However, it’s very simple to ``.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. + +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 `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 + + import telethon.tl.functions as tl + # We now have access to 'tl.messages.SendMessageRequest' + +We see that this request must take at least two parameters, a ``peer`` +of type `InputPeer`__, and a ``message`` which is just a Python +``str``\ ing. + +How can we retrieve this ``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 ``.get_input_entity()``: + + .. code-block:: python + + peer = client.get_input_entity('someone') + +When you’re going to invoke an API method, most require you to pass an +``InputUser``, ``InputChat``, or so on, this is why using +``.get_input_entity()`` is more straightforward (and sometimes +immediate, if you know the ID of the user for instance). If you also +need to have information about the whole user, use ``.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 ``.get_input_peer``: + + .. code-block:: python + + from telethon import utils + peer = utils.get_input_user(entity) + +After this small parenthesis about ``.get_entity`` versus +``.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 nearly 250 methods available as of layer 73, +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!' + )) + + +.. 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 as many items as possible". + 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 +__ https://lonamiwebs.github.io/Telethon/methods/messages/send_message.html +__ https://lonamiwebs.github.io/Telethon/types/input_peer.html +__ https://lonamiwebs.github.io/Telethon/constructors/input_peer_user.html \ No newline at end of file diff --git a/readthedocs/extra/basic/creating-a-client.rst b/readthedocs/extra/basic/creating-a-client.rst new file mode 100644 index 00000000..997386db --- /dev/null +++ b/readthedocs/extra/basic/creating-a-client.rst @@ -0,0 +1,76 @@ +.. _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 be changed later as far as I'm aware. + +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 + + # Use your own values here + api_id = 12345 + api_hash = '0123456789abcdef0123456789abcdef' + phone_number = '+34600000000' + + 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 simply a JSON file which you can (but shouldn't) modify. + +Before using the client, you must be connected to Telegram. Doing so is very easy: + + ``client.connect() # Must return True, otherwise, try again`` + +You may or may not be authorized yet. You must be authorized before you're able to send any request: + + ``client.is_user_authorized() # Returns True if you can send requests`` + +If you're not authorized, you need to ``.sign_in()``: + + .. code-block:: python + + 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. + +``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! + +.. 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`__. + + +__ https://github.com/Anorov/PySocks#installation +__ https://github.com/Anorov/PySocks#usage-1%3E \ No newline at end of file diff --git a/readthedocs/extra/basic/getting-started.rst b/readthedocs/extra/basic/getting-started.rst new file mode 100644 index 00000000..ce4354fd --- /dev/null +++ b/readthedocs/extra/basic/getting-started.rst @@ -0,0 +1,54 @@ +.. 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. + + +================= +Getting Started! +================= + +Simple Installation +********************* + + ``pip install telethon`` + +**More details**: :ref:`installation` + + +Creating a client +************** + + .. code-block:: python + + from telethon import TelegramClient + + # 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' + phone = '+34600000000' + + client = TelegramClient('session_name', api_id, api_hash) + client.connect() + + # If you already have a previous 'session_name.session' file, skip this. + client.sign_in(phone=phone) + me = client.sign_in(code=77777) # Put whatever code you received here. + +**More details**: `Click here `_ + + +Simple Stuff +************** + .. code-block:: python + + print(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) + total, messages, senders = client.get_message_history('username') + client.download_media(messages[0]) + diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst new file mode 100644 index 00000000..f302e8f7 --- /dev/null +++ b/readthedocs/extra/basic/installation.rst @@ -0,0 +1,82 @@ +.. _installation: + +================= +Installation +================= + + +Automatic Installation +^^^^^^^^^^^^^^^^^^^^^^^ +To install Telethon, simply do: + + ``pip install telethon`` + +If you get something like ``"SyntaxError: invalid syntax"`` or any other error while installing, it's probably because ``pip`` defaults to Python 2, which is not supported. Use ``pip3`` instead. + +If you already have the library installed, upgrade with: + + ``pip install --upgrade telethon``. + +You can also install the library directly from GitHub or a fork: + + .. code-block:: python + + # pip 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. + + +Manual Installation +^^^^^^^^^^^^^^^^^^^^ + +1. Install the required ``pyaes`` (`GitHub`__ | `PyPi`__) and ``rsa`` (`GitHub`__ | `PyPi`__) modules: + + ``sudo -H pip install pyaes rsa`` + +2. Clone Telethon's GitHub repository: ``git clone https://github.com/LonamiWebs/Telethon.git`` + +3. Enter the cloned repository: ``cd Telethon`` + +4. Run the code generator: ``python3 setup.py gen_tl`` + +5. Done! + +To generate the documentation, ``cd docs`` and then ``python3 generate.py``. + + +Optional dependencies +^^^^^^^^^^^^^^^^^^^^^^^^ + +If you're using the library under ARM (or even if you aren't), +you may want to install ``sympy`` through ``pip`` for a substantial speed-up +when generating the keys required to connect to Telegram +(you can of course do this on desktop too). See `issue #199`__ for more. + +If ``libssl`` is available on your system, it will also be used wherever encryption is needed. + +If neither of these are available, a pure Python callback will be used instead, +so you can still run the library wherever Python is available! + + +Sending Requests +***************** + `Here `__ + +Working with updates +********************** + `Here `__ + +Accessing the full API +*********************** + `Here `__ + + +__ 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://github.com/LonamiWebs/Telethon/issues/199 \ No newline at end of file diff --git a/readthedocs/extra/basic/sending-requests.rst b/readthedocs/extra/basic/sending-requests.rst new file mode 100644 index 00000000..160e2259 --- /dev/null +++ b/readthedocs/extra/basic/sending-requests.rst @@ -0,0 +1,55 @@ +.. _sending-requests: + +================== +Sending Requests +================== + +Since we're working with Python, one must not forget that they 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 on the ``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: + + ``myself = client.get_me()`` + +Internally, this method has sent a request to Telegram, who replied with the information about your own user. + +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') + +Note that saving and using these entities will be more important when Accessing the Full API. +For now, this is a good way to get information about an user or chat. + +Other common methods for quick scripts are also available: + + .. code-block:: python + + # Sending a message (use an entity/username/etc) + client.send_message('TheAyyBot', 'ayy') + + # Sending a photo, or a file + client.send_file(myself, '/path/to/the/file.jpg', force_document=True) + + # Downloading someone's profile photo. File is saved to 'where' + where = client.download_profile_photo(someone) + + # Retrieving the message history + total, messages, senders = client.get_message_history(someone) + + # Downloading the media from a specific message + # You can specify either a directory, a filename, or nothing at all + where = client.download_media(message, '/path/to/output') + +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. diff --git a/readthedocs/extra/basic/sessions.rst b/readthedocs/extra/basic/sessions.rst new file mode 100644 index 00000000..0f9d458a --- /dev/null +++ b/readthedocs/extra/basic/sessions.rst @@ -0,0 +1,48 @@ +.. _sessions: + +============== +Session Files +============== + +The first parameter you pass the the constructor of the +``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 on the +working directory. + +These JSON session files 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 (users with their +name, username, chats and so on) **in memory**, not to disk, so that you +can quickly access them by username or phone number. This can be +disabled too. Run ``help(client.session.entities)`` to see the available +methods (or ``help(EntityDatabase)``). + +If you’re not going to work without 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``. + +If you don’t want to save the files as JSON, you can also create your +custom ``Session`` subclass and override the ``.save()`` and ``.load()`` +methods. For example, you could save it on a database: + + .. code-block:: python + + class DatabaseSession(Session): + def save(): + # serialize relevant data to the database + + def load(): + # load relevant data to the database + +You should read the ``session.py`` source file to know what “relevant +data” you need to keep track of. \ No newline at end of file diff --git a/readthedocs/extra/basic/working-with-updates.rst b/readthedocs/extra/basic/working-with-updates.rst new file mode 100644 index 00000000..4367bbc4 --- /dev/null +++ b/readthedocs/extra/basic/working-with-updates.rst @@ -0,0 +1,133 @@ +==================== +Working with Updates +==================== + +.. contents:: + + +The library can run in four distinguishable modes: + +- With no extra threads at all. +- With an extra thread that receives everything as soon as possible (default). +- With several worker threads that run your update handlers. +- A mix of the above. + +Since this section is about updates, we'll describe the simplest way to work with them. + +.. warning:: + Remember that you should always call ``client.disconnect()`` once you're done. + + +Using multiple workers +^^^^^^^^^^^^^^^^^^^^^^^ + +When you create your client, simply pass a number to the ``update_workers`` parameter: + + ``client = TelegramClient('session', api_id, api_hash, update_workers=4)`` + +4 workers should suffice for most cases (this is also the default on `Python Telegram Bot`__). +You can set this value to more, or even less if you need. + +The next thing you want to do is to add a method that will be called when an `Update`__ arrives: + + .. code-block:: python + + def callback(update): + print('I received', update) + + client.add_update_handler(callback) + # do more work here, or simply sleep! + +That's it! Now let's do something more interesting. +Every time an user talks to use, let's reply to them with the same text reversed: + + .. code-block:: python + + from telethon.tl.types import UpdateShortMessage, PeerUser + + def replier(update): + if isinstance(update, UpdateShortMessage) and not update.out: + client.send_message(PeerUser(update.user_id), update.message[::-1]) + + + client.add_update_handler(replier) + input('Press enter to stop this!') + client.disconnect() + +We only ask you one thing: don't keep this running for too long, or your contacts will go mad. + + +Spawning no worker at all +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All the workers do is loop forever and poll updates from a queue that is filled from the ``ReadThread``, +responsible for reading every item off the network. +If you only need a worker and the ``MainThread`` would be doing no other job, +this is the preferred way. You can easily do the same as the workers like so: + + .. code-block:: python + + while True: + try: + update = client.updates.poll() + if not update: + continue + + print('I received', update) + except KeyboardInterrupt: + break + + client.disconnect() + +Note that ``poll`` accepts a ``timeout=`` parameter, +and it will return ``None`` if other thread got the update before you could or if the timeout expired, +so it's important to check ``if not update``. + +This can coexist with the rest of ``N`` workers, or you can set it to ``0`` additional workers: + + ``client = TelegramClient('session', api_id, api_hash, update_workers=0)`` + +You **must** set it to ``0`` (or other number), as it defaults to ``None`` and there is a different. +``None`` workers means updates won't be processed *at all*, +so you must set it to some value (0 or greater) if you want ``client.updates.poll()`` to work. + + +Using the main thread instead the ``ReadThread`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you have no work to do on the ``MainThread`` and you were planning to have a ``while True: sleep(1)``, +don't do that. Instead, don't spawn the secondary ``ReadThread`` at all like so: + + .. code-block:: python + + client = TelegramClient( + ... + spawn_read_thread=False + ) + +And then ``.idle()`` from the ``MainThread``: + + ``client.idle()`` + +You can stop it with :kbd:`Control+C`, +and you can configure the signals to be used in a similar fashion to `Python Telegram Bot`__. + +As a complete example: + + .. code-block:: python + + def callback(update): + print('I received', update) + + client = TelegramClient('session', api_id, api_hash, + update_workers=1, spawn_read_thread=False) + + client.connect() + client.add_update_handler(callback) + client.idle() # ends with Ctrl+C + client.disconnect() + + +__ https://python-telegram-bot.org/ +__ https://lonamiwebs.github.io/Telethon/types/update.html +__ https://github.com/python-telegram-bot/python-telegram-bot/blob/4b3315db6feebafb94edcaa803df52bb49999ced/telegram/ext/updater.py#L460 \ No newline at end of file diff --git a/readthedocs/extra/getting_started.rst b/readthedocs/extra/getting_started.rst deleted file mode 100644 index 3a6b36a8..00000000 --- a/readthedocs/extra/getting_started.rst +++ /dev/null @@ -1,103 +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. - - -================= -Getting Started! -================= - -Installation -************** - -To install Telethon, simply do: - - ``pip install telethon`` - -If you get something like ``"SyntaxError: invalid syntax"`` or any other error while installing, it's probably because ``pip`` defaults to Python 2, which is not supported. Use ``pip3`` instead. - -If you already have the library installed, upgrade with: - - ``pip install --upgrade telethon``. - -You can also install the library directly from GitHub or a fork: - - .. code-block:: python - - # pip 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. - - - - -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 be changed later as far as I'm aware. - -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 - - # 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' - phone = '+34600000000' - - client = TelegramClient('session_name', api_id, api_hash) - client.connect() - - # If you already have a previous 'session_name.session' file, skip this. - client.sign_in(phone=phone) - me = client.sign_in(code=77777) # Put whatever code you received here. - -**More details**: `Click here `_ - - -Simple Stuff -************** - .. code-block:: python - - print(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) - total, messages, senders = client.get_message_history('username') - client.download_media(messages[0]) - - -Diving In -************** - -.. note:: More info in our Wiki! - -Sending Requests -^^^^^^^^^^^^^^^^^^^^^ - `Here `__ - -Working with updates -^^^^^^^^^^^^^^^^^^^^^ - `Here `__ - -Accessing the full API -^^^^^^^^^^^^^^^^^^^^^^^ - `Here `__ - diff --git a/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst b/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst new file mode 100644 index 00000000..1ad3da19 --- /dev/null +++ b/readthedocs/extra/troubleshooting/deleted-limited-or-deactivated-accounts.rst @@ -0,0 +1,26 @@ +========================================= +Deleted, Limited or Deactivated Accounts +========================================= + +If you're from Iran or Russian, 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. + +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 \ No newline at end of file diff --git a/readthedocs/extra/troubleshooting/enable-logging.rst b/readthedocs/extra/troubleshooting/enable-logging.rst new file mode 100644 index 00000000..a6d45d00 --- /dev/null +++ b/readthedocs/extra/troubleshooting/enable-logging.rst @@ -0,0 +1,24 @@ +================ +Enable Logging +================ + +Telethon makes use of the `logging`__ module, and you can enable it as follows: + + .. code-block:: python + + import logging + logging.basicConfig(level=logging.DEBUG) + +You can also use it in 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!') + + +__ https://docs.python.org/3/library/logging.html \ No newline at end of file diff --git a/readthedocs/extra/troubleshooting/rpc-errors.rst b/readthedocs/extra/troubleshooting/rpc-errors.rst new file mode 100644 index 00000000..6e8a59f0 --- /dev/null +++ b/readthedocs/extra/troubleshooting/rpc-errors.rst @@ -0,0 +1,27 @@ +========== +RPC Errors +========== + +RPC stands for Remote Procedure Call, and when Telethon raises an +``RPCError``, it’s most likely because you have invoked some of the API +methods incorrectly (wrong parameters, wrong permissions, or even +something went wrong on Telegram’s server). The most common are: + +- ``FloodError`` (420), the same request was repeated many times. Must + wait ``.seconds``. +- ``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``. \ No newline at end of file diff --git a/readthedocs/index.rst b/readthedocs/index.rst index 8f036adc..b5c77e6b 100644 --- a/readthedocs/index.rst +++ b/readthedocs/index.rst @@ -10,24 +10,49 @@ Pure Python 3 Telegram client library. Official Site `here Date: Mon, 20 Nov 2017 12:19:53 +0800 Subject: [PATCH 14/22] final fix for readthedocs --- .../extra/advanced-usage/users-and-chats.rst | 1 + readthedocs/extra/basic/accessing-the-full-api.rst | 2 ++ readthedocs/extra/basic/installation.rst | 14 +++++++++++--- readthedocs/extra/basic/working-with-updates.rst | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/readthedocs/extra/advanced-usage/users-and-chats.rst b/readthedocs/extra/advanced-usage/users-and-chats.rst index 3f3e0729..a48a2857 100644 --- a/readthedocs/extra/advanced-usage/users-and-chats.rst +++ b/readthedocs/extra/advanced-usage/users-and-chats.rst @@ -6,6 +6,7 @@ Users and Chats Make sure you have gone through :ref:`prelude` already! .. contents:: + :depth: 2 .. _retrieving-an-entity: diff --git a/readthedocs/extra/basic/accessing-the-full-api.rst b/readthedocs/extra/basic/accessing-the-full-api.rst index ceeea7d8..ab6682db 100644 --- a/readthedocs/extra/basic/accessing-the-full-api.rst +++ b/readthedocs/extra/basic/accessing-the-full-api.rst @@ -1,3 +1,5 @@ +.. _accessing-the-full-api: + ========================== Accessing the Full API ========================== diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst index f302e8f7..ab9b44a3 100644 --- a/readthedocs/extra/basic/installation.rst +++ b/readthedocs/extra/basic/installation.rst @@ -64,15 +64,23 @@ so you can still run the library wherever Python is available! Sending Requests ***************** - `Here `__ + + .. note:: + Read here: :ref:`sending-requests` + Working with updates ********************** - `Here `__ + + .. note:: + Read here: :ref:`working-with-updates` + Accessing the full API *********************** - `Here `__ + + .. note:: + Read here: :ref:`accessing-the-full-api` __ https://github.com/ricmoo/pyaes diff --git a/readthedocs/extra/basic/working-with-updates.rst b/readthedocs/extra/basic/working-with-updates.rst index 4367bbc4..c5d9e919 100644 --- a/readthedocs/extra/basic/working-with-updates.rst +++ b/readthedocs/extra/basic/working-with-updates.rst @@ -1,3 +1,5 @@ +.. _working-with-updates: + ==================== Working with Updates ==================== From 07d642a354829693d1eb79cb3d7da962a48bbfcd Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 20 Nov 2017 13:58:42 +0800 Subject: [PATCH 15/22] tiny fix for readthedocs --- readthedocs/extra/basic/installation.rst | 25 +++--------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/readthedocs/extra/basic/installation.rst b/readthedocs/extra/basic/installation.rst index ab9b44a3..ecad699b 100644 --- a/readthedocs/extra/basic/installation.rst +++ b/readthedocs/extra/basic/installation.rst @@ -11,11 +11,12 @@ To install Telethon, simply do: ``pip install telethon`` -If you get something like ``"SyntaxError: invalid syntax"`` or any other error while installing, it's probably because ``pip`` defaults to Python 2, which is not supported. Use ``pip3`` instead. +If you get something like ``"SyntaxError: invalid syntax"`` or any other error while installing, +it's probably because ``pip`` defaults to Python 2, which is not supported. Use ``pip3`` instead. If you already have the library installed, upgrade with: - ``pip install --upgrade telethon``. + ``pip install --upgrade telethon`` You can also install the library directly from GitHub or a fork: @@ -62,26 +63,6 @@ If neither of these are available, a pure Python callback will be used instead, so you can still run the library wherever Python is available! -Sending Requests -***************** - - .. note:: - Read here: :ref:`sending-requests` - - -Working with updates -********************** - - .. note:: - Read here: :ref:`working-with-updates` - - -Accessing the full API -*********************** - - .. note:: - Read here: :ref:`accessing-the-full-api` - __ https://github.com/ricmoo/pyaes __ https://pypi.python.org/pypi/pyaes From 9f033be05a9af4a7804d0bcb19c3ab6235c56c4a Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 20 Nov 2017 17:26:31 +0800 Subject: [PATCH 16/22] fix in-docs links --- readthedocs/extra/basic/getting-started.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readthedocs/extra/basic/getting-started.rst b/readthedocs/extra/basic/getting-started.rst index ce4354fd..bad3ea30 100644 --- a/readthedocs/extra/basic/getting-started.rst +++ b/readthedocs/extra/basic/getting-started.rst @@ -13,7 +13,7 @@ Simple Installation ``pip install telethon`` -**More details**: :ref:`installation` + **More details**: :ref:`installation` Creating a client @@ -36,7 +36,7 @@ Creating a client client.sign_in(phone=phone) me = client.sign_in(code=77777) # Put whatever code you received here. -**More details**: `Click here `_ + **More details**: :ref:`creating-a-client` Simple Stuff From 152856dfbc9e3f8b4b29b171bad306236d7fc754 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 20 Nov 2017 10:58:11 +0100 Subject: [PATCH 17/22] Add a force_fetch parameter to .get_entity --- telethon/telegram_client.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/telethon/telegram_client.py b/telethon/telegram_client.py index ae864ea6..fc4b4342 100644 --- a/telethon/telegram_client.py +++ b/telethon/telegram_client.py @@ -996,7 +996,7 @@ class TelegramClient(TelegramBareClient): # region Small utilities to make users' life easier - def get_entity(self, entity): + def get_entity(self, entity, force_fetch=False): """ Turns the given entity into a valid Telegram user or chat. @@ -1014,12 +1014,20 @@ class TelegramClient(TelegramBareClient): If the entity is neither, and it's not a TLObject, an error will be raised. + + :param force_fetch: + If True, the entity cache is bypassed and the entity is fetched + again with an API call. Defaults to False to avoid unnecessary + calls, but since a cached version would be returned, the entity + may be out of date. :return: """ - try: - return self.session.entities[entity] - except KeyError: - pass + if not force_fetch: + # Try to use cache unless we want to force a fetch + try: + return self.session.entities[entity] + except KeyError: + pass if isinstance(entity, int) or ( isinstance(entity, TLObject) and From 7d4453351babd62df70f9ebe96d59e016eff1737 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Tue, 21 Nov 2017 12:56:53 +0100 Subject: [PATCH 18/22] Update .gitignore to include docs/_build --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 156d23e3..f2090e85 100755 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -.idea +# Docs +_build/ + +# Generated code telethon/tl/functions/ telethon/tl/types/ telethon/tl/all_tlobjects.py From f99d14558fc964f37fa321f9f598e7fef83adba4 Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Thu, 23 Nov 2017 02:06:43 +1000 Subject: [PATCH 19/22] binary_reader: Parse TL 'date' to UTC datetime instead of local --- telethon/extensions/binary_reader.py | 2 +- telethon/tl/tlobject.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/telethon/extensions/binary_reader.py b/telethon/extensions/binary_reader.py index 2355c6a4..c5abcbf9 100644 --- a/telethon/extensions/binary_reader.py +++ b/telethon/extensions/binary_reader.py @@ -113,7 +113,7 @@ class BinaryReader: into a Python datetime object """ value = self.read_int() - return None if value == 0 else datetime.fromtimestamp(value) + return None if value == 0 else datetime.utcfromtimestamp(value) def tgread_object(self): """Reads a Telegram object""" diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index 1bd57878..2ba6ef14 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -36,7 +36,7 @@ class TLObject: ', '.join(TLObject.pretty_format(x) for x in obj) ) elif isinstance(obj, datetime): - return 'datetime.fromtimestamp({})'.format(obj.timestamp()) + return 'datetime.utcfromtimestamp({})'.format(obj.timestamp()) else: return repr(obj) else: @@ -81,7 +81,7 @@ class TLObject: result.append(']') elif isinstance(obj, datetime): - result.append('datetime.fromtimestamp(') + result.append('datetime.utcfromtimestamp(') result.append(repr(obj.timestamp())) result.append(')') From 5a4d6d4a570cc71afb553e914bf7fc7d2366544f Mon Sep 17 00:00:00 2001 From: "Dmitry D. Chernov" Date: Thu, 23 Nov 2017 22:39:35 +1000 Subject: [PATCH 20/22] tlobject: Represent timestamp as 'int' instead of 'float' --- telethon/tl/tlobject.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/telethon/tl/tlobject.py b/telethon/tl/tlobject.py index 2ba6ef14..68c5e741 100644 --- a/telethon/tl/tlobject.py +++ b/telethon/tl/tlobject.py @@ -36,7 +36,9 @@ class TLObject: ', '.join(TLObject.pretty_format(x) for x in obj) ) elif isinstance(obj, datetime): - return 'datetime.utcfromtimestamp({})'.format(obj.timestamp()) + return 'datetime.utcfromtimestamp({})'.format( + int(obj.timestamp()) + ) else: return repr(obj) else: @@ -82,7 +84,7 @@ class TLObject: elif isinstance(obj, datetime): result.append('datetime.utcfromtimestamp(') - result.append(repr(obj.timestamp())) + result.append(repr(int(obj.timestamp()))) result.append(')') else: From b42b4bb3267bcbce297d79fc2f292f0fc548fb87 Mon Sep 17 00:00:00 2001 From: Maxim Smirnov Date: Fri, 24 Nov 2017 20:47:36 +0300 Subject: [PATCH 21/22] Add OpenSSL assertion on authenticator.py (#453) aes_ige.c(88): OpenSSL internal error, assertion failed --- telethon/network/authenticator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index 1accf493..8c508ef9 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -113,6 +113,8 @@ def _do_authentication(connection): key, iv = utils.generate_key_data_from_nonce( res_pq.server_nonce, new_nonce ) + if server_dh_params.encrypted_answer % 16 != 0: + raise SecurityError('AES block size missmatch') plain_text_answer = AES.decrypt_ige( server_dh_params.encrypted_answer, key, iv ) From 74ec6391d92fc1ee75b738a4af74ae5e47f494f4 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Fri, 24 Nov 2017 19:05:18 +0100 Subject: [PATCH 22/22] Fix-up security assertion (b42b4bb for #453) --- telethon/network/authenticator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index 8c508ef9..00a28fdf 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -113,8 +113,10 @@ def _do_authentication(connection): key, iv = utils.generate_key_data_from_nonce( res_pq.server_nonce, new_nonce ) - if server_dh_params.encrypted_answer % 16 != 0: - raise SecurityError('AES block size missmatch') + if len(server_dh_params.encrypted_answer) % 16 != 0: + # See PR#453 + raise SecurityError('AES block size mismatch') + plain_text_answer = AES.decrypt_ige( server_dh_params.encrypted_answer, key, iv )