From c1a40630a3477ad9b03b44e009ca57928de887d7 Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Sat, 15 Jun 2019 20:57:33 +0200 Subject: [PATCH] Replace python-proxy aiosocks Although it supports a lot less features, aiosocks' code is much cleaner, and python-proxy has several asyncio-related issues. Furthermore, python-proxy author claims: > "pproxy is mostly used standalone" > (in https://github.com/qwj/python-proxy/issues/43) --- readthedocs/basic/signing-in.rst | 26 ++++++++++------ telethon/client/telegrambaseclient.py | 9 +++--- telethon/helpers.py | 37 ----------------------- telethon/network/connection/connection.py | 26 +++++++++++++--- telethon_examples/print_messages.py | 2 +- telethon_examples/print_updates.py | 2 +- 6 files changed, 44 insertions(+), 58 deletions(-) diff --git a/readthedocs/basic/signing-in.rst b/readthedocs/basic/signing-in.rst index 84915676..46184955 100644 --- a/readthedocs/basic/signing-in.rst +++ b/readthedocs/basic/signing-in.rst @@ -107,7 +107,7 @@ Signing In behind a Proxy ========================= If you need to use a proxy to access Telegram, -you will need to `install python-proxy`__ and then change: +you will need to `install aiosocks`__ and then change: .. code-block:: python @@ -117,20 +117,26 @@ with .. code-block:: python - TelegramClient('anon', api_id, api_hash, proxy='socks5://127.0.0.1:4444') - - # You can also use a ``dict`` (it will be translated to the same URI as above) TelegramClient('anon', api_id, api_hash, proxy={ - 'scheme': 'socks5', - 'hostname': '127.0.0.1', + 'host': '127.0.0.1', 'port': 4444 }) (of course, replacing the IP and port with the IP and port of the proxy). -The ``proxy=`` argument should be a tuple, a list or a dict, -consisting of parameters described `in python-proxy usage`__. +The ``proxy=`` argument should be a dictionary +where the following keys are allowed: -.. __: https://github.com/qwj/python-proxy#quickstart -.. __: https://github.com/qwj/python-proxy#uri-syntax +.. code-block:: python + + proxy = { + 'host': 'localhost', # (mandatory) proxy IP address + 'port': 42252, # (mandatory) proxy port number + 'protocol': 'socks5', # (optional) protocol to use, default socks5, allowed values: socks5, socks4 + 'username': 'foo', # (optional) username if the proxy requires auth + 'password': 'bar', # (optional) password if the proxy requires auth + 'remote_resolve': True # (optional) whether to use remote or local resolve, default remote + } + +.. __: https://github.com/nibrag/aiosocks diff --git a/telethon/client/telegrambaseclient.py b/telethon/client/telegrambaseclient.py index 8de83724..f07bfe9e 100644 --- a/telethon/client/telegrambaseclient.py +++ b/telethon/client/telegrambaseclient.py @@ -67,11 +67,10 @@ class TelegramBaseClient(abc.ABC): By default this is ``False`` as IPv6 support is not too widespread yet. - proxy (`str` | `dict`, optional): - The URI with all the required proxy information, or a dictionary - with the fields to use (like ``schemepython``, ``hostname``, etc.). + proxy (`dict`, optional): + A dictionary with information about the proxy to connect to. - See https://github.com/qwj/python-proxy#uri-syntax for details. + See :ref:`signing-in` for details. timeout (`int` | `float`, optional): The timeout in seconds to be used when connecting. @@ -253,7 +252,7 @@ class TelegramBaseClient(abc.ABC): self._request_retries = request_retries self._connection_retries = connection_retries self._retry_delay = retry_delay or 0 - self._proxy = helpers._get_proxy_uri(proxy) + self._proxy = proxy self._timeout = timeout self._auto_reconnect = auto_reconnect diff --git a/telethon/helpers.py b/telethon/helpers.py index 185ebf6f..f3f8c044 100644 --- a/telethon/helpers.py +++ b/telethon/helpers.py @@ -131,43 +131,6 @@ def _sync_exit(self, *args): return loop.run_until_complete(self.__aexit__(*args)) -def _get_proxy_uri(proxy): - if not proxy: - return None - elif isinstance(proxy, str): - return proxy - elif isinstance(proxy, dict): - fmt = { - 'scheme': 'socks5', - 'cipher': '', - 'netloc': '', - 'localbind': '', - 'plugins': '', - 'rules': '', - 'auth': '' - } - fmt.update(proxy) - if 'hostname' in fmt: - fmt['netloc'] = '{}:{}'.format(fmt.pop('hostname'), fmt.pop('port')) - if 'username' in fmt: - fmt['auth'] = '{}:{}'.format(fmt.pop('username'), fmt.pop('password')) - - fix = { - 'cipher': '{}@', - 'localbind': '@{}', - 'plugins': ',{}', - 'rules': '?{}', - 'auth': '#{}' - } - for k, v in fix.items(): - if fmt[k]: - fmt[k] = v.format(fmt[k]) - - return '{scheme}://{cipher}{netloc}/{localbind}{plugins}{rules}{auth}'.format(**fmt) - else: - raise TypeError('{!r} is not a valid proxy (must be str URI or dict)'.format(proxy)) - - # endregion # region Cryptographic related utils diff --git a/telethon/network/connection/connection.py b/telethon/network/connection/connection.py index 174349df..422a2881 100644 --- a/telethon/network/connection/connection.py +++ b/telethon/network/connection/connection.py @@ -46,11 +46,29 @@ class Connection(abc.ABC): connect_coroutine = asyncio.open_connection( self._ip, self._port, loop=self._loop, ssl=ssl) else: - import pproxy + import aiosocks - # FIXME https://github.com/qwj/python-proxy/issues/41 - connect_coroutine = pproxy.Connection( - self._proxy).tcp_connect(self._ip, self._port) + auth = None + proto = self._proxy.get('protocol', 'socks5').lower() + if proto == 'socks5': + proxy = aiosocks.Socks5Addr(self._proxy['host'], self._proxy['port']) + if 'username' in self._proxy: + auth = aiosocks.Socks5Auth(self._proxy['username'], self._proxy['password']) + + elif proto == 'socks4': + proxy = aiosocks.Socks4Addr(self._proxy['host'], self._proxy['port']) + if 'username' in self._proxy: + auth = aiosocks.Socks4Auth(self._proxy['username']) + + else: + raise ValueError('Unsupported proxy protocol {}'.format(self._proxy['protocol'])) + + connect_coroutine = aiosocks.open_connection( + proxy=proxy, + proxy_auth=auth, + dst=(self._ip, self._port), + remote_resolve=self._proxy.get('remote_resolve', True) + ) self._reader, self._writer = await asyncio.wait_for( connect_coroutine, diff --git a/telethon_examples/print_messages.py b/telethon_examples/print_messages.py index 25f71e2f..068a7203 100644 --- a/telethon_examples/print_messages.py +++ b/telethon_examples/print_messages.py @@ -22,7 +22,7 @@ def get_env(name, message, cast=str): session = os.environ.get('TG_SESSION', 'printer') api_id = get_env('TG_API_ID', 'Enter your API ID: ', int) api_hash = get_env('TG_API_HASH', 'Enter your API hash: ') -proxy = None # https://github.com/qwj/python-proxy#uri-syntax +proxy = None # https://docs.telethon.dev/en/latest/basic/signing-in.html#signing-in-behind-a-proxy # Create and start the client so we can make requests (we don't here) client = TelegramClient(session, api_id, api_hash, proxy=proxy).start() diff --git a/telethon_examples/print_updates.py b/telethon_examples/print_updates.py index fff8efcc..6a43e5ae 100755 --- a/telethon_examples/print_updates.py +++ b/telethon_examples/print_updates.py @@ -27,7 +27,7 @@ def get_env(name, message, cast=str): session = os.environ.get('TG_SESSION', 'printer') api_id = get_env('TG_API_ID', 'Enter your API ID: ', int) api_hash = get_env('TG_API_HASH', 'Enter your API hash: ') -proxy = None # https://github.com/qwj/python-proxy#uri-syntax +proxy = None # https://docs.telethon.dev/en/latest/basic/signing-in.html#signing-in-behind-a-proxy # This is our update handler. It is called when a new update arrives.