mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-01 10:49:58 +03:00
Replace PySocks with aiosocks due to bugs (see #1192)
related to blocking behaviour of PySocks on socket connection. Also small fixes and additional checks on local_addr parameter. Updated documentation.
This commit is contained in:
parent
9c833b601a
commit
e4eaecb1cd
|
@ -1,4 +1,3 @@
|
||||||
cryptg
|
cryptg
|
||||||
pysocks
|
|
||||||
hachoir
|
hachoir
|
||||||
pillow
|
pillow
|
||||||
|
|
|
@ -117,7 +117,7 @@ Signing In behind a Proxy
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
If you need to use a proxy to access Telegram,
|
If you need to use a proxy to access Telegram,
|
||||||
you will need to `install PySocks`__ and then change:
|
you will need to `install aiosocks`__ and then change:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -127,15 +127,36 @@ with
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
TelegramClient('anon', api_id, api_hash, proxy=(socks.SOCKS5, '127.0.0.1', 4444))
|
TelegramClient('anon', api_id, api_hash, proxy=("socks5", '127.0.0.1', 4444))
|
||||||
|
|
||||||
(of course, replacing the IP and port with the IP and port of the proxy).
|
(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,
|
The ``proxy=`` argument should be a tuple, a list or a dict,
|
||||||
consisting of parameters described `in PySocks usage`__.
|
consisting of parameters described `in aiosocks usage`__.
|
||||||
|
|
||||||
.. __: https://github.com/Anorov/PySocks#installation
|
Example:
|
||||||
.. __: https://github.com/Anorov/PySocks#usage-1
|
|
||||||
|
* .. code-block:: python
|
||||||
|
|
||||||
|
proxy = ('socks5', '1.1.1.1', 5555, 'foo', 'bar', True)
|
||||||
|
|
||||||
|
* .. code-block:: python
|
||||||
|
|
||||||
|
proxy = ['socks5', '1.1.1.1', 5555, 'foo', 'bar', True]
|
||||||
|
|
||||||
|
* .. code-block:: python
|
||||||
|
|
||||||
|
proxy = {
|
||||||
|
'protocol': 'socks5', # (mandatory) protocol to use, default socks5, allowed values: 'socks5' (or 2), 'socks4' (or 1)
|
||||||
|
'host': '1.1.1.1', # (mandatory) proxy IP address
|
||||||
|
'port': 5555, # (mandatory) proxy port number
|
||||||
|
'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#installation
|
||||||
|
.. __: https://github.com/nibrag/aiosocks#usage
|
||||||
|
|
||||||
|
|
||||||
Using MTProto Proxies
|
Using MTProto Proxies
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
aiosocks
|
||||||
pyaes
|
pyaes
|
||||||
rsa
|
rsa
|
||||||
|
|
|
@ -107,11 +107,11 @@ class TelegramBaseClient(abc.ABC):
|
||||||
An iterable consisting of the proxy info. If `connection` is
|
An iterable consisting of the proxy info. If `connection` is
|
||||||
one of `MTProxy`, then it should contain MTProxy credentials:
|
one of `MTProxy`, then it should contain MTProxy credentials:
|
||||||
``('hostname', port, 'secret')``. Otherwise, it's meant to store
|
``('hostname', port, 'secret')``. Otherwise, it's meant to store
|
||||||
function parameters for PySocks, like ``(type, 'hostname', port)``.
|
function parameters for AioSocks, like ``(type, 'hostname', port)``.
|
||||||
See https://github.com/Anorov/PySocks#usage-1 for more.
|
See https://github.com/nibrag/aiosocks for more.
|
||||||
|
|
||||||
local_addr (`str`, optional):
|
local_addr (`str` | `tuple`, optional):
|
||||||
Local host address used to bind the socket to locally.
|
Local host address (and port, optionally) used to bind the socket to locally.
|
||||||
You only need to use this if you have multiple network cards and
|
You only need to use this if you have multiple network cards and
|
||||||
want to use a specific one.
|
want to use a specific one.
|
||||||
|
|
||||||
|
@ -220,10 +220,10 @@ class TelegramBaseClient(abc.ABC):
|
||||||
connection: 'typing.Type[Connection]' = ConnectionTcpFull,
|
connection: 'typing.Type[Connection]' = ConnectionTcpFull,
|
||||||
use_ipv6: bool = False,
|
use_ipv6: bool = False,
|
||||||
proxy: typing.Union[tuple, dict] = None,
|
proxy: typing.Union[tuple, dict] = None,
|
||||||
local_addr=None,
|
local_addr: typing.Union[tuple, str] = None,
|
||||||
timeout: int = 10,
|
timeout: int = 10,
|
||||||
request_retries: int = 5,
|
request_retries: int = 5,
|
||||||
connection_retries: int =5,
|
connection_retries: int = 5,
|
||||||
retry_delay: int = 1,
|
retry_delay: int = 1,
|
||||||
auto_reconnect: bool = True,
|
auto_reconnect: bool = True,
|
||||||
sequential_updates: bool = False,
|
sequential_updates: bool = False,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import abc
|
import abc
|
||||||
import asyncio
|
import asyncio
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -11,6 +10,20 @@ except ImportError:
|
||||||
from ...errors import InvalidChecksumError
|
from ...errors import InvalidChecksumError
|
||||||
from ... import helpers
|
from ... import helpers
|
||||||
|
|
||||||
|
import aiosocks
|
||||||
|
|
||||||
|
# For some reason, `aiosocks` internal errors are not inherited from
|
||||||
|
# builtin IOError (just from Exception). Instead of adding those
|
||||||
|
# in exceptions clauses everywhere through the code, we
|
||||||
|
# rather monkey-patch them in place.
|
||||||
|
|
||||||
|
aiosocks.errors.SocksError = ConnectionError
|
||||||
|
aiosocks.errors.NoAcceptableAuthMethods = ConnectionError
|
||||||
|
aiosocks.errors.LoginAuthenticationFailed = ConnectionError
|
||||||
|
aiosocks.errors.InvalidServerVersion = ConnectionError
|
||||||
|
aiosocks.errors.InvalidServerReply = ConnectionError
|
||||||
|
aiosocks.errors.SocksConnectionError = ConnectionError
|
||||||
|
|
||||||
|
|
||||||
class Connection(abc.ABC):
|
class Connection(abc.ABC):
|
||||||
"""
|
"""
|
||||||
|
@ -46,58 +59,76 @@ class Connection(abc.ABC):
|
||||||
self._recv_queue = asyncio.Queue(1)
|
self._recv_queue = asyncio.Queue(1)
|
||||||
|
|
||||||
async def _connect(self, timeout=None, ssl=None):
|
async def _connect(self, timeout=None, ssl=None):
|
||||||
if not self._proxy:
|
|
||||||
if self._local_addr is not None:
|
|
||||||
local_addr = (self._local_addr, None)
|
|
||||||
else:
|
|
||||||
local_addr = None
|
|
||||||
|
|
||||||
self._reader, self._writer = await asyncio.wait_for(
|
if self._local_addr is not None:
|
||||||
asyncio.open_connection(self._ip, self._port, ssl=ssl, local_addr=local_addr),
|
|
||||||
timeout=timeout
|
# NOTE: If port is not specified, we use 0 port
|
||||||
)
|
# to notify the OS that port should be chosen randomly
|
||||||
|
# from the available ones.
|
||||||
|
|
||||||
|
if isinstance(self._local_addr, tuple) and len(self._local_addr) == 2:
|
||||||
|
local_addr = self._local_addr
|
||||||
|
elif isinstance(self._local_addr, str):
|
||||||
|
local_addr = (self._local_addr, 0)
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown local address format: {}".format(self._local_addr))
|
||||||
else:
|
else:
|
||||||
import socks
|
local_addr = None
|
||||||
if ':' in self._ip:
|
|
||||||
mode, address = socket.AF_INET6, (self._ip, self._port, 0, 0)
|
if not self._proxy:
|
||||||
|
connect_coroutine = asyncio.open_connection(
|
||||||
|
host=self._ip,
|
||||||
|
port=self._port,
|
||||||
|
ssl=ssl,
|
||||||
|
local_addr=local_addr)
|
||||||
|
else:
|
||||||
|
|
||||||
|
if isinstance(self._proxy, (tuple, list)):
|
||||||
|
proxy, auth, remote_resolve = self._parse_proxy(*self._proxy)
|
||||||
|
elif isinstance(self._proxy, dict):
|
||||||
|
proxy, auth, remote_resolve = self._parse_proxy(**self._proxy)
|
||||||
else:
|
else:
|
||||||
mode, address = socket.AF_INET, (self._ip, self._port)
|
raise ValueError("Unknown proxy format: {}".format(self._proxy.__class__.__name__))
|
||||||
|
|
||||||
s = socks.socksocket(mode, socket.SOCK_STREAM)
|
connect_coroutine = aiosocks.open_connection(
|
||||||
if isinstance(self._proxy, dict):
|
proxy=proxy,
|
||||||
s.set_proxy(**self._proxy)
|
proxy_auth=auth,
|
||||||
else:
|
dst=(self._ip, self._port),
|
||||||
s.set_proxy(*self._proxy)
|
remote_resolve=remote_resolve,
|
||||||
|
ssl=ssl,
|
||||||
s.settimeout(timeout)
|
local_addr=local_addr)
|
||||||
if self._local_addr is not None:
|
|
||||||
s.bind((self._local_addr, None))
|
|
||||||
await asyncio.wait_for(
|
|
||||||
asyncio.get_event_loop().sock_connect(s, address),
|
|
||||||
timeout=timeout
|
|
||||||
)
|
|
||||||
if ssl:
|
|
||||||
if ssl_mod is None:
|
|
||||||
raise RuntimeError(
|
|
||||||
'Cannot use proxy that requires SSL'
|
|
||||||
'without the SSL module being available'
|
|
||||||
)
|
|
||||||
|
|
||||||
s = ssl_mod.wrap_socket(
|
|
||||||
s,
|
|
||||||
do_handshake_on_connect=True,
|
|
||||||
ssl_version=ssl_mod.PROTOCOL_SSLv23,
|
|
||||||
ciphers='ADH-AES256-SHA'
|
|
||||||
)
|
|
||||||
|
|
||||||
s.setblocking(False)
|
|
||||||
|
|
||||||
self._reader, self._writer = await asyncio.open_connection(sock=s)
|
|
||||||
|
|
||||||
|
self._reader, self._writer = await asyncio.wait_for(connect_coroutine, timeout=timeout)
|
||||||
self._codec = self.packet_codec(self)
|
self._codec = self.packet_codec(self)
|
||||||
self._init_conn()
|
self._init_conn()
|
||||||
await self._writer.drain()
|
await self._writer.drain()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_proxy(protocol, host, port, username=None, password=None, remote_resolve=True):
|
||||||
|
|
||||||
|
proxy, auth = None, None
|
||||||
|
|
||||||
|
if isinstance(protocol, str):
|
||||||
|
protocol = protocol.lower()
|
||||||
|
|
||||||
|
# We do the check for numerical values here
|
||||||
|
# to be backwards compatible with PySocks proxy format,
|
||||||
|
# (since socks.SOCKS5 = 2 and socks.SOCKS4 = 1)
|
||||||
|
|
||||||
|
if protocol == 'socks5' or protocol == 2:
|
||||||
|
proxy = aiosocks.Socks5Addr(host, port)
|
||||||
|
if username and password:
|
||||||
|
auth = aiosocks.Socks5Auth(username, password)
|
||||||
|
|
||||||
|
elif protocol == 'socks4' or protocol == 1:
|
||||||
|
proxy = aiosocks.Socks4Addr(host, port)
|
||||||
|
if username:
|
||||||
|
auth = aiosocks.Socks4Auth(username)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported proxy protocol {}'.format(protocol))
|
||||||
|
|
||||||
|
return proxy, auth, remote_resolve
|
||||||
|
|
||||||
async def connect(self, timeout=None, ssl=None):
|
async def connect(self, timeout=None, ssl=None):
|
||||||
"""
|
"""
|
||||||
Establishes a connection with the server.
|
Establishes a connection with the server.
|
||||||
|
|
|
@ -22,7 +22,7 @@ def get_env(name, message, cast=str):
|
||||||
session = os.environ.get('TG_SESSION', 'printer')
|
session = os.environ.get('TG_SESSION', 'printer')
|
||||||
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
||||||
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
||||||
proxy = None # https://github.com/Anorov/PySocks
|
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)
|
# Create and start the client so we can make requests (we don't here)
|
||||||
client = TelegramClient(session, api_id, api_hash, proxy=proxy).start()
|
client = TelegramClient(session, api_id, api_hash, proxy=proxy).start()
|
||||||
|
|
|
@ -27,7 +27,7 @@ def get_env(name, message, cast=str):
|
||||||
session = os.environ.get('TG_SESSION', 'printer')
|
session = os.environ.get('TG_SESSION', 'printer')
|
||||||
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
api_id = get_env('TG_API_ID', 'Enter your API ID: ', int)
|
||||||
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
api_hash = get_env('TG_API_HASH', 'Enter your API hash: ')
|
||||||
proxy = None # https://github.com/Anorov/PySocks
|
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.
|
# This is our update handler. It is called when a new update arrives.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user