/pyrogram/connection/transport/tcp/tcp.py

https://github.com/pyrogram/pyrogram · Python · 120 lines · 81 code · 20 blank · 19 comment · 23 complexity · 9d4420f6f50bdb0e52d6db00a3843a27 MD5 · raw file

  1. # Pyrogram - Telegram MTProto API Client Library for Python
  2. # Copyright (C) 2017-2020 Dan <https://github.com/delivrance>
  3. #
  4. # This file is part of Pyrogram.
  5. #
  6. # Pyrogram is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Lesser General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # Pyrogram is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Lesser General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public License
  17. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
  18. import asyncio
  19. import ipaddress
  20. import logging
  21. import socket
  22. import time
  23. try:
  24. import socks
  25. except ImportError as e:
  26. e.msg = (
  27. "PySocks is missing and Pyrogram can't run without. "
  28. "Please install it using \"pip3 install pysocks\"."
  29. )
  30. raise e
  31. log = logging.getLogger(__name__)
  32. class TCP:
  33. TIMEOUT = 10
  34. def __init__(self, ipv6: bool, proxy: dict):
  35. self.socket = None
  36. self.reader = None # type: asyncio.StreamReader
  37. self.writer = None # type: asyncio.StreamWriter
  38. self.lock = asyncio.Lock()
  39. if proxy.get("enabled", False):
  40. hostname = proxy.get("hostname", None)
  41. port = proxy.get("port", None)
  42. try:
  43. ip_address = ipaddress.ip_address(hostname)
  44. except ValueError:
  45. self.socket = socks.socksocket(socket.AF_INET)
  46. else:
  47. if isinstance(ip_address, ipaddress.IPv6Address):
  48. self.socket = socks.socksocket(socket.AF_INET6)
  49. else:
  50. self.socket = socks.socksocket(socket.AF_INET)
  51. self.socket.set_proxy(
  52. proxy_type=socks.SOCKS5,
  53. addr=hostname,
  54. port=port,
  55. username=proxy.get("username", None),
  56. password=proxy.get("password", None)
  57. )
  58. log.info(f"Using proxy {hostname}:{port}")
  59. else:
  60. self.socket = socks.socksocket(
  61. socket.AF_INET6 if ipv6
  62. else socket.AF_INET
  63. )
  64. self.socket.settimeout(TCP.TIMEOUT)
  65. async def connect(self, address: tuple):
  66. self.socket.connect(address)
  67. self.reader, self.writer = await asyncio.open_connection(sock=self.socket)
  68. def close(self):
  69. try:
  70. self.writer.close()
  71. except AttributeError:
  72. try:
  73. self.socket.shutdown(socket.SHUT_RDWR)
  74. except OSError:
  75. pass
  76. finally:
  77. # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout.
  78. # This is a workaround that seems to fix the occasional delayed stop of a client.
  79. time.sleep(0.001)
  80. self.socket.close()
  81. async def send(self, data: bytes):
  82. async with self.lock:
  83. self.writer.write(data)
  84. await self.writer.drain()
  85. async def recv(self, length: int = 0):
  86. data = b""
  87. while len(data) < length:
  88. try:
  89. chunk = await asyncio.wait_for(
  90. self.reader.read(length - len(data)),
  91. TCP.TIMEOUT
  92. )
  93. except (OSError, asyncio.TimeoutError):
  94. return None
  95. else:
  96. if chunk:
  97. data += chunk
  98. else:
  99. return None
  100. return data