From de39c181ef7b0b54e70b95f7848d2c1272ad2b9a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 8 Jun 2018 13:10:07 +0200 Subject: [PATCH 001/155] Start refactoring Connection to accommodate asyncio --- pyrogram/connection/connection.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index a53295ce..b03e8852 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -16,9 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import threading -import time from .transport import * @@ -36,23 +35,23 @@ class Connection: 4: TCPIntermediateO } - def __init__(self, address: tuple, proxy: dict, mode: int = 1): + def __init__(self, address: tuple, proxy: dict, mode: int = 2): self.address = address self.proxy = proxy self.mode = self.MODES.get(mode, TCPAbridged) - self.lock = threading.Lock() + self.connection = None - def connect(self): + async def connect(self): for i in range(Connection.MAX_RETRIES): self.connection = self.mode(self.proxy) try: log.info("Connecting...") - self.connection.connect(self.address) + await self.connection.connect(self.address) except OSError: self.connection.close() - time.sleep(1) + await asyncio.sleep(1) else: break else: @@ -62,9 +61,8 @@ class Connection: self.connection.close() log.info("Disconnected") - def send(self, data: bytes): - with self.lock: - self.connection.sendall(data) + async def send(self, data: bytes): + await self.connection.send(data) - def recv(self) -> bytes or None: - return self.connection.recvall() + async def recv(self) -> bytes or None: + return await self.connection.recv() From 7a6d7d003798cf26450b347c6d4c9743309fc47f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 9 Jun 2018 19:36:23 +0200 Subject: [PATCH 002/155] Implement async TCP protocol --- pyrogram/connection/transport/tcp/tcp.py | 86 ++++++++++++++++++++---- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 5df8aacb..f006cadd 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging import socket @@ -32,14 +33,18 @@ except ImportError as e: log = logging.getLogger(__name__) -class TCP(socks.socksocket): +class TCP: def __init__(self, proxy: dict): - super().__init__() - self.settimeout(10) + self.proxy = proxy + + self.socket = socks.socksocket() + self.reader = None # type: asyncio.StreamReader + self.writer = None # type: asyncio.StreamWriter + self.proxy_enabled = proxy.get("enabled", False) if proxy and self.proxy_enabled: - self.set_proxy( + self.socket.set_proxy( proxy_type=socks.SOCKS5, addr=proxy.get("hostname", None), port=proxy.get("port", None), @@ -52,26 +57,81 @@ class TCP(socks.socksocket): proxy.get("port", None) )) + async def connect(self, address: tuple): + self.socket.connect(address) + self.reader, self.writer = await asyncio.open_connection(sock=self.socket) + def close(self): try: - self.shutdown(socket.SHUT_RDWR) - except OSError: - pass - finally: - super().close() + self.writer.close() + except AttributeError: + try: + self.socket.shutdown(socket.SHUT_RDWR) + except OSError: + pass + finally: + self.socket.close() - def recvall(self, length: int) -> bytes or None: + async def send(self, data: bytes): + self.writer.write(data) + await self.writer.drain() + + async def recv(self, length: int = 0): data = b"" while len(data) < length: try: - packet = super().recv(length - len(data)) + chunk = await self.reader.read(length - len(data)) except OSError: return None else: - if packet: - data += packet + if chunk: + data += chunk else: return None return data + +# class TCP(socks.socksocket): +# def __init__(self, proxy: dict): +# super().__init__() +# self.settimeout(10) +# self.proxy_enabled = proxy.get("enabled", False) +# +# if proxy and self.proxy_enabled: +# self.set_proxy( +# proxy_type=socks.SOCKS5, +# addr=proxy.get("hostname", None), +# port=proxy.get("port", None), +# username=proxy.get("username", None), +# password=proxy.get("password", None) +# ) +# +# log.info("Using proxy {}:{}".format( +# proxy.get("hostname", None), +# proxy.get("port", None) +# )) +# +# def close(self): +# try: +# self.shutdown(socket.SHUT_RDWR) +# except OSError: +# pass +# finally: +# super().close() +# +# def recvall(self, length: int) -> bytes or None: +# data = b"" +# +# while len(data) < length: +# try: +# packet = super().recv(length - len(data)) +# except OSError: +# return None +# else: +# if packet: +# data += packet +# else: +# return None +# +# return data From dc322ddf1a6ee07aad0264f8d35e1a55b0958064 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 10 Jun 2018 16:14:30 +0200 Subject: [PATCH 003/155] Expose TCP class --- pyrogram/connection/transport/tcp/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index ce662e61..016e91e4 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .tcp import TCP from .tcp_abridged import TCPAbridged from .tcp_abridged_o import TCPAbridgedO from .tcp_full import TCPFull From 6ab60c0d3609493627caef9fcbf7f08a9034132d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 10 Jun 2018 16:14:42 +0200 Subject: [PATCH 004/155] Add type hint --- pyrogram/connection/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index b03e8852..cab31b62 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -40,7 +40,7 @@ class Connection: self.proxy = proxy self.mode = self.MODES.get(mode, TCPAbridged) - self.connection = None + self.connection = None # type: TCP async def connect(self): for i in range(Connection.MAX_RETRIES): From ead0b4f029561fa55e57805de25425fa98ca6c09 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 10 Jun 2018 16:15:19 +0200 Subject: [PATCH 005/155] Use more relevant names for Connection fields --- pyrogram/connection/connection.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index cab31b62..b4705fca 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -40,17 +40,17 @@ class Connection: self.proxy = proxy self.mode = self.MODES.get(mode, TCPAbridged) - self.connection = None # type: TCP + self.protocol = None # type: TCP async def connect(self): for i in range(Connection.MAX_RETRIES): - self.connection = self.mode(self.proxy) + self.protocol = self.mode(self.proxy) try: log.info("Connecting...") - await self.connection.connect(self.address) + await self.protocol.connect(self.address) except OSError: - self.connection.close() + self.protocol.close() await asyncio.sleep(1) else: break @@ -58,11 +58,11 @@ class Connection: raise TimeoutError def close(self): - self.connection.close() + self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): - await self.connection.send(data) + await self.protocol.send(data) async def recv(self) -> bytes or None: - return await self.connection.recv() + return await self.protocol.recv() From d64337bf90466efc57d6da7665d97002fb73c014 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Jun 2018 12:25:30 +0200 Subject: [PATCH 006/155] Implement Intermediate protocol using asyncio --- .../transport/tcp/tcp_intermediate.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index 4b2e2596..82c7b605 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -28,19 +28,23 @@ class TCPIntermediate(TCP): def __init__(self, proxy: dict): super().__init__(proxy) - def connect(self, address: tuple): - super().connect(address) - super().sendall(b"\xee" * 4) + async def connect(self, address: tuple): + await super().connect(address) + await super().send(b"\xee" * 4) - log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) + log.info("Connected{}!".format( + " with proxy" + if self.proxy_enabled + else "" + )) - def sendall(self, data: bytes, *args): - super().sendall(pack(" bytes or None: - length = super().recvall(4) + async def recv(self, length: int = 0) -> bytes or None: + length = await super().recv(4) if length is None: return None - return super().recvall(unpack(" Date: Tue, 12 Jun 2018 15:56:33 +0200 Subject: [PATCH 007/155] Start rewriting Session using asyncio --- pyrogram/session/session.py | 547 +++++++++++++++++++++++++++++++----- 1 file changed, 473 insertions(+), 74 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 7e90cfff..173e8846 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -16,16 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging import platform import threading -import time -from datetime import timedelta, datetime +from datetime import datetime, timedelta from hashlib import sha1, sha256 from io import BytesIO from os import urandom -from queue import Queue -from threading import Event, Thread import pyrogram from pyrogram import __copyright__, __license__, __version__ @@ -43,7 +41,7 @@ log = logging.getLogger(__name__) class Result: def __init__(self): self.value = None - self.event = Event() + self.event = asyncio.Event() class Session: @@ -115,47 +113,38 @@ class Session: self.pending_acks = set() - self.recv_queue = Queue() + self.recv_queue = asyncio.Queue() self.results = {} - self.ping_thread = None - self.ping_thread_event = Event() + self.ping_task = None + self.ping_task_event = asyncio.Event() - self.next_salt_thread = None - self.next_salt_thread_event = Event() + self.next_salt_task = None + self.next_salt_task_event = asyncio.Event() - self.net_worker_list = [] + self.net_worker_task = None + self.recv_task = None - self.is_connected = Event() + self.is_connected = asyncio.Event() - def start(self): + async def start(self): while True: self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy) try: - self.connection.connect() + await self.connection.connect() - for i in range(self.NET_WORKERS): - self.net_worker_list.append( - Thread( - target=self.net_worker, - name="NetWorker#{}".format(i + 1) - ) - ) - - self.net_worker_list[-1].start() - - Thread(target=self.recv, name="RecvThread").start() + self.net_worker_task = asyncio.ensure_future(self.net_worker()) + self.recv_task = asyncio.ensure_future(self.recv()) self.current_salt = FutureSalt(0, 0, self.INITIAL_SALT) - self.current_salt = FutureSalt(0, 0, self._send(functions.Ping(0)).new_server_salt) - self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] + self.current_salt = FutureSalt(0, 0, (await self._send(functions.Ping(0))).new_server_salt) + self.current_salt = (await self._send(functions.GetFutureSalts(1))).salts[0] - self.next_salt_thread = Thread(target=self.next_salt, name="NextSaltThread") - self.next_salt_thread.start() + self.next_salt_task = asyncio.ensure_future(self.next_salt()) if not self.is_cdn: - self._send( + await self._send( functions.InvokeWithLayer( layer, functions.InitConnection( @@ -169,14 +158,13 @@ class Session: ) ) - self.ping_thread = Thread(target=self.ping, name="PingThread") - self.ping_thread.start() + self.ping_task = asyncio.ensure_future(self.ping()) log.info("Connection inited: Layer {}".format(layer)) except (OSError, TimeoutError, Error): - self.stop() + await self.stop() except Exception as e: - self.stop() + await self.stop() raise e else: break @@ -185,30 +173,28 @@ class Session: log.debug("Session started") - def stop(self): + async def stop(self): self.is_connected.clear() - self.ping_thread_event.set() - self.next_salt_thread_event.set() + self.ping_task_event.set() + self.next_salt_task_event.set() - if self.ping_thread is not None: - self.ping_thread.join() + if self.ping_task is not None: + await self.ping_task - if self.next_salt_thread is not None: - self.next_salt_thread.join() + if self.next_salt_task is not None: + await self.next_salt_task - self.ping_thread_event.clear() - self.next_salt_thread_event.clear() + self.ping_task_event.clear() + self.next_salt_task_event.clear() self.connection.close() - for i in range(self.NET_WORKERS): - self.recv_queue.put(None) + await self.recv_task - for i in self.net_worker_list: - i.join() + self.recv_queue.put_nowait(None) - self.net_worker_list.clear() + await self.net_worker_task for i in self.results.values(): i.event.set() @@ -260,12 +246,12 @@ class Session: return message - def net_worker(self): + async def net_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) while True: - packet = self.recv_queue.get() + packet = await self.recv_queue.get() if packet is None: break @@ -315,7 +301,7 @@ class Session: log.info("Send {} acks".format(len(self.pending_acks))) try: - self._send(types.MsgsAck(list(self.pending_acks)), False) + await self._send(types.MsgsAck(list(self.pending_acks)), False) except (OSError, TimeoutError): pass else: @@ -325,13 +311,16 @@ class Session: log.debug("{} stopped".format(name)) - def ping(self): - log.debug("PingThread started") + async def ping(self): + log.debug("Ping Task started") while True: - self.ping_thread_event.wait(self.PING_INTERVAL) + try: + await asyncio.wait_for(self.ping_task_event.wait(), self.PING_INTERVAL) + except asyncio.TimeoutError: + pass - if self.ping_thread_event.is_set(): + if self.ping_task_event.is_set(): break try: @@ -341,9 +330,9 @@ class Session: except (OSError, TimeoutError, Error): pass - log.debug("PingThread stopped") + log.debug("Ping Task stopped") - def next_salt(self): + async def next_salt(self): log.debug("NextSaltThread started") while True: @@ -360,38 +349,42 @@ class Session: now + timedelta(seconds=dt) )) - self.next_salt_thread_event.wait(dt) + try: + await asyncio.wait_for(self.next_salt_task_event.wait(), dt) + except asyncio.TimeoutError: + pass - if self.next_salt_thread_event.is_set(): + if self.next_salt_task_event.is_set(): break try: - self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] + self.current_salt = (await self._send(functions.GetFutureSalts(1))).salts[0] except (OSError, TimeoutError, Error): self.connection.close() break log.debug("NextSaltThread stopped") - def recv(self): - log.debug("RecvThread started") + async def recv(self): + log.debug("Recv Task started") while True: - packet = self.connection.recv() + packet = await self.connection.recv() if packet is None or len(packet) == 4: if packet: log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): - Thread(target=self.restart, name="RestartThread").start() + asyncio.ensure_future(self.restart()) + break - self.recv_queue.put(packet) + self.recv_queue.put_nowait(packet) - log.debug("RecvThread stopped") + log.debug("Recv Task stopped") - def _send(self, data: Object, wait_response: bool = True): + async def _send(self, data: Object, wait_response: bool = True): message = self.msg_factory(data) msg_id = message.msg_id @@ -401,13 +394,17 @@ class Session: payload = self.pack(message) try: - self.connection.send(payload) + await self.connection.send(payload) except OSError as e: self.results.pop(msg_id, None) raise e if wait_response: - self.results[msg_id].event.wait(self.WAIT_TIMEOUT) + try: + await asyncio.wait_for(self.results[msg_id].event.wait(), self.WAIT_TIMEOUT) + except asyncio.TimeoutError: + pass + result = self.results.pop(msg_id).value if result is None: @@ -422,11 +419,14 @@ class Session: else: return result - def send(self, data: Object, retries: int = MAX_RETRIES): - self.is_connected.wait(self.WAIT_TIMEOUT) + async def send(self, data: Object, retries: int = MAX_RETRIES): + try: + await asyncio.wait_for(self.is_connected.wait(), self.WAIT_TIMEOUT) + except asyncio.TimeoutError: + pass try: - return self._send(data) + return await self._send(data) except (OSError, TimeoutError, InternalServerError) as e: if retries == 0: raise e from None @@ -436,5 +436,404 @@ class Session: Session.MAX_RETRIES - retries, datetime.now(), type(data))) - time.sleep(0.5) - return self.send(data, retries - 1) + await asyncio.sleep(0.5) + return await self.send(data, retries - 1) + +# class Result: +# def __init__(self): +# self.value = None +# self.event = Event() +# +# +# class Session: +# VERSION = __version__ +# APP_VERSION = "Pyrogram \U0001f525 {}".format(VERSION) +# +# DEVICE_MODEL = "{} {}".format( +# platform.python_implementation(), +# platform.python_version() +# ) +# +# SYSTEM_VERSION = "{} {}".format( +# platform.system(), +# platform.release() +# ) +# +# INITIAL_SALT = 0x616e67656c696361 +# NET_WORKERS = 1 +# WAIT_TIMEOUT = 15 +# MAX_RETRIES = 5 +# ACKS_THRESHOLD = 8 +# PING_INTERVAL = 5 +# +# notice_displayed = False +# +# BAD_MSG_DESCRIPTION = { +# 16: "[16] msg_id too low, the client time has to be synchronized", +# 17: "[17] msg_id too high, the client time has to be synchronized", +# 18: "[18] incorrect two lower order msg_id bits, the server expects client message msg_id to be divisible by 4", +# 19: "[19] container msg_id is the same as msg_id of a previously received message", +# 20: "[20] message too old, it cannot be verified by the server", +# 32: "[32] msg_seqno too low", +# 33: "[33] msg_seqno too high", +# 34: "[34] an even msg_seqno expected, but odd received", +# 35: "[35] odd msg_seqno expected, but even received", +# 48: "[48] incorrect server salt", +# 64: "[64] invalid container" +# } +# +# def __init__(self, +# dc_id: int, +# test_mode: bool, +# proxy: dict, +# auth_key: bytes, +# api_id: int, +# is_cdn: bool = False, +# client: pyrogram = None): +# if not Session.notice_displayed: +# print("Pyrogram v{}, {}".format(__version__, __copyright__)) +# print("Licensed under the terms of the " + __license__, end="\n\n") +# Session.notice_displayed = True +# +# self.dc_id = dc_id +# self.test_mode = test_mode +# self.proxy = proxy +# self.api_id = api_id +# self.is_cdn = is_cdn +# self.client = client +# +# self.connection = None +# +# self.auth_key = auth_key +# self.auth_key_id = sha1(auth_key).digest()[-8:] +# +# self.session_id = Long(MsgId()) +# self.msg_factory = MsgFactory() +# +# self.current_salt = None +# +# self.pending_acks = set() +# +# self.recv_queue = Queue() +# self.results = {} +# +# self.ping_thread = None +# self.ping_thread_event = Event() +# +# self.next_salt_thread = None +# self.next_salt_thread_event = Event() +# +# self.net_worker_list = [] +# +# self.is_connected = Event() +# +# def start(self): +# while True: +# self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy) +# +# try: +# self.connection.connect() +# +# for i in range(self.NET_WORKERS): +# self.net_worker_list.append( +# Thread( +# target=self.net_worker, +# name="NetWorker#{}".format(i + 1) +# ) +# ) +# +# self.net_worker_list[-1].start() +# +# Thread(target=self.recv, name="RecvThread").start() +# +# self.current_salt = FutureSalt(0, 0, self.INITIAL_SALT) +# self.current_salt = FutureSalt(0, 0, self._send(functions.Ping(0)).new_server_salt) +# self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] +# +# self.next_salt_thread = Thread(target=self.next_salt, name="NextSaltThread") +# self.next_salt_thread.start() +# +# if not self.is_cdn: +# self._send( +# functions.InvokeWithLayer( +# layer, +# functions.InitConnection( +# self.api_id, +# self.DEVICE_MODEL, +# self.SYSTEM_VERSION, +# self.APP_VERSION, +# "en", "", "en", +# functions.help.GetConfig(), +# ) +# ) +# ) +# +# self.ping_thread = Thread(target=self.ping, name="PingThread") +# self.ping_thread.start() +# +# log.info("Connection inited: Layer {}".format(layer)) +# except (OSError, TimeoutError, Error): +# self.stop() +# except Exception as e: +# self.stop() +# raise e +# else: +# break +# +# self.is_connected.set() +# +# log.debug("Session started") +# +# def stop(self): +# self.is_connected.clear() +# +# self.ping_thread_event.set() +# self.next_salt_thread_event.set() +# +# if self.ping_thread is not None: +# self.ping_thread.join() +# +# if self.next_salt_thread is not None: +# self.next_salt_thread.join() +# +# self.ping_thread_event.clear() +# self.next_salt_thread_event.clear() +# +# self.connection.close() +# +# for i in range(self.NET_WORKERS): +# self.recv_queue.put(None) +# +# for i in self.net_worker_list: +# i.join() +# +# self.net_worker_list.clear() +# +# for i in self.results.values(): +# i.event.set() +# +# if self.client and callable(self.client.disconnect_handler): +# try: +# self.client.disconnect_handler(self.client) +# except Exception as e: +# log.error(e, exc_info=True) +# +# log.debug("Session stopped") +# +# def restart(self): +# self.stop() +# self.start() +# +# def pack(self, message: Message): +# data = Long(self.current_salt.salt) + self.session_id + message.write() +# padding = urandom(-(len(data) + 12) % 16 + 12) +# +# # 88 = 88 + 0 (outgoing message) +# msg_key_large = sha256(self.auth_key[88: 88 + 32] + data + padding).digest() +# msg_key = msg_key_large[8:24] +# aes_key, aes_iv = KDF(self.auth_key, msg_key, True) +# +# return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) +# +# def unpack(self, b: BytesIO) -> Message: +# assert b.read(8) == self.auth_key_id, b.getvalue() +# +# msg_key = b.read(16) +# aes_key, aes_iv = KDF(self.auth_key, msg_key, False) +# data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) +# data.read(8) +# +# # https://core.telegram.org/mtproto/security_guidelines#checking-session-id +# assert data.read(8) == self.session_id +# +# message = Message.read(data) +# +# # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key +# # https://core.telegram.org/mtproto/security_guidelines#checking-message-length +# # 96 = 88 + 8 (incoming message) +# assert msg_key == sha256(self.auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] +# +# # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id +# # TODO: check for lower msg_ids +# assert message.msg_id % 2 != 0 +# +# return message +# +# def net_worker(self): +# name = threading.current_thread().name +# log.debug("{} started".format(name)) +# +# while True: +# packet = self.recv_queue.get() +# +# if packet is None: +# break +# +# try: +# data = self.unpack(BytesIO(packet)) +# +# messages = ( +# data.body.messages +# if isinstance(data.body, MsgContainer) +# else [data] +# ) +# +# log.debug(data) +# +# for msg in messages: +# if msg.seq_no % 2 != 0: +# if msg.msg_id in self.pending_acks: +# continue +# else: +# self.pending_acks.add(msg.msg_id) +# +# if isinstance(msg.body, (types.MsgDetailedInfo, types.MsgNewDetailedInfo)): +# self.pending_acks.add(msg.body.answer_msg_id) +# continue +# +# if isinstance(msg.body, types.NewSessionCreated): +# continue +# +# msg_id = None +# +# if isinstance(msg.body, (types.BadMsgNotification, types.BadServerSalt)): +# msg_id = msg.body.bad_msg_id +# elif isinstance(msg.body, (core.FutureSalts, types.RpcResult)): +# msg_id = msg.body.req_msg_id +# elif isinstance(msg.body, types.Pong): +# msg_id = msg.body.msg_id +# else: +# if self.client is not None: +# self.client.updates_queue.put(msg.body) +# +# if msg_id in self.results: +# self.results[msg_id].value = getattr(msg.body, "result", msg.body) +# self.results[msg_id].event.set() +# +# if len(self.pending_acks) >= self.ACKS_THRESHOLD: +# log.info("Send {} acks".format(len(self.pending_acks))) +# +# try: +# self._send(types.MsgsAck(list(self.pending_acks)), False) +# except (OSError, TimeoutError): +# pass +# else: +# self.pending_acks.clear() +# except Exception as e: +# log.error(e, exc_info=True) +# +# log.debug("{} stopped".format(name)) +# +# def ping(self): +# log.debug("PingThread started") +# +# while True: +# self.ping_thread_event.wait(self.PING_INTERVAL) +# +# if self.ping_thread_event.is_set(): +# break +# +# try: +# self._send(functions.PingDelayDisconnect( +# 0, self.WAIT_TIMEOUT + 10 +# ), False) +# except (OSError, TimeoutError, Error): +# pass +# +# log.debug("PingThread stopped") +# +# def next_salt(self): +# log.debug("NextSaltThread started") +# +# while True: +# now = datetime.now() +# +# # Seconds to wait until middle-overlap, which is +# # 15 minutes before/after the current/next salt end/start time +# dt = (self.current_salt.valid_until - now).total_seconds() - 900 +# +# log.debug("Current salt: {} | Next salt in {:.0f}m {:.0f}s ({})".format( +# self.current_salt.salt, +# dt // 60, +# dt % 60, +# now + timedelta(seconds=dt) +# )) +# +# self.next_salt_thread_event.wait(dt) +# +# if self.next_salt_thread_event.is_set(): +# break +# +# try: +# self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] +# except (OSError, TimeoutError, Error): +# self.connection.close() +# break +# +# log.debug("NextSaltThread stopped") +# +# def recv(self): +# log.debug("RecvThread started") +# +# while True: +# packet = self.connection.recv() +# +# if packet is None or len(packet) == 4: +# if packet: +# log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) +# +# if self.is_connected.is_set(): +# Thread(target=self.restart, name="RestartThread").start() +# break +# +# self.recv_queue.put(packet) +# +# log.debug("RecvThread stopped") +# +# def _send(self, data: Object, wait_response: bool = True): +# message = self.msg_factory(data) +# msg_id = message.msg_id +# +# if wait_response: +# self.results[msg_id] = Result() +# +# payload = self.pack(message) +# +# try: +# self.connection.send(payload) +# except OSError as e: +# self.results.pop(msg_id, None) +# raise e +# +# if wait_response: +# self.results[msg_id].event.wait(self.WAIT_TIMEOUT) +# result = self.results.pop(msg_id).value +# +# if result is None: +# raise TimeoutError +# elif isinstance(result, types.RpcError): +# Error.raise_it(result, type(data)) +# elif isinstance(result, types.BadMsgNotification): +# raise Exception(self.BAD_MSG_DESCRIPTION.get( +# result.error_code, +# "Error code {}".format(result.error_code) +# )) +# else: +# return result +# +# def send(self, data: Object, retries: int = MAX_RETRIES): +# self.is_connected.wait(self.WAIT_TIMEOUT) +# +# try: +# return self._send(data) +# except (OSError, TimeoutError, InternalServerError) as e: +# if retries == 0: +# raise e from None +# +# (log.warning if retries < 3 else log.info)( +# "{}: {} Retrying {}".format( +# Session.MAX_RETRIES - retries, +# datetime.now(), type(data))) +# +# time.sleep(0.5) +# return self.send(data, retries - 1) From e333e8dada2d887f582cf3a8c0abb62ed11b41ec Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Jun 2018 20:00:19 +0200 Subject: [PATCH 008/155] First step of Client conversion using asyncio --- pyrogram/client/client.py | 105 +++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 73f45966..8eba760a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -34,7 +34,6 @@ from configparser import ConfigParser from datetime import datetime from hashlib import sha256, md5 from signal import signal, SIGINT, SIGTERM, SIGABRT -from threading import Thread from pyrogram.api import functions, types from pyrogram.api.core import Object @@ -169,7 +168,7 @@ class Client(Methods, BaseClient): self._proxy["enabled"] = True self._proxy.update(value) - def start(self, debug: bool = False): + async def start(self, debug: bool = False): """Use this method to start the Client after creating it. Requires no parameters. @@ -200,7 +199,7 @@ class Client(Methods, BaseClient): client=self ) - self.session.start() + await self.session.start() self.is_started = True if self.user_id is None: @@ -224,66 +223,66 @@ class Client(Methods, BaseClient): self.send(functions.messages.GetPinnedDialogs()) self.get_dialogs_chunk(0) else: - self.send(functions.updates.GetState()) + await self.send(functions.updates.GetState()) - for i in range(self.UPDATES_WORKERS): - self.updates_workers_list.append( - Thread( - target=self.updates_worker, - name="UpdatesWorker#{}".format(i + 1) - ) - ) - - self.updates_workers_list[-1].start() - - for i in range(self.DOWNLOAD_WORKERS): - self.download_workers_list.append( - Thread( - target=self.download_worker, - name="DownloadWorker#{}".format(i + 1) - ) - ) - - self.download_workers_list[-1].start() - - self.dispatcher.start() + # for i in range(self.UPDATES_WORKERS): + # self.updates_workers_list.append( + # Thread( + # target=self.updates_worker, + # name="UpdatesWorker#{}".format(i + 1) + # ) + # ) + # + # self.updates_workers_list[-1].start() + # + # for i in range(self.DOWNLOAD_WORKERS): + # self.download_workers_list.append( + # Thread( + # target=self.download_worker, + # name="DownloadWorker#{}".format(i + 1) + # ) + # ) + # + # self.download_workers_list[-1].start() + # + # self.dispatcher.start() mimetypes.init() - Syncer.add(self) + # Syncer.add(self) - def stop(self): + async def stop(self): """Use this method to manually stop the Client. Requires no parameters. """ if not self.is_started: raise ConnectionError("Client is already stopped") - Syncer.remove(self) - self.dispatcher.stop() - - for _ in range(self.DOWNLOAD_WORKERS): - self.download_queue.put(None) - - for i in self.download_workers_list: - i.join() - - self.download_workers_list.clear() - - for _ in range(self.UPDATES_WORKERS): - self.updates_queue.put(None) - - for i in self.updates_workers_list: - i.join() - - self.updates_workers_list.clear() - - for i in self.media_sessions.values(): - i.stop() - - self.media_sessions.clear() + # Syncer.remove(self) + # self.dispatcher.stop() + # + # for _ in range(self.DOWNLOAD_WORKERS): + # self.download_queue.put(None) + # + # for i in self.download_workers_list: + # i.join() + # + # self.download_workers_list.clear() + # + # for _ in range(self.UPDATES_WORKERS): + # self.updates_queue.put(None) + # + # for i in self.updates_workers_list: + # i.join() + # + # self.updates_workers_list.clear() + # + # for i in self.media_sessions.values(): + # i.stop() + # + # self.media_sessions.clear() self.is_started = False - self.session.stop() + await self.session.stop() def add_handler(self, handler, group: int = 0): """Use this method to register an update handler. @@ -812,7 +811,7 @@ class Client(Methods, BaseClient): self.stop() - def send(self, data: Object): + async def send(self, data: Object): """Use this method to send Raw Function queries. This method makes possible to manually call every single Telegram API method in a low-level manner. @@ -829,7 +828,7 @@ class Client(Methods, BaseClient): if not self.is_started: raise ConnectionError("Client has not been started") - r = self.session.send(data) + r = await self.session.send(data) self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) From f76c654548cc958be89176d4d83ab7f7fc20377d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Jun 2018 20:02:02 +0200 Subject: [PATCH 009/155] Add TODO --- pyrogram/connection/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index b4705fca..a9aba7c1 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -28,6 +28,7 @@ class Connection: MAX_RETRIES = 3 MODES = { + # TODO: Implement other protocols using asyncio 0: TCPFull, 1: TCPAbridged, 2: TCPIntermediate, From a9ccbaca19e61669fa4f6b33d8fa65f505132252 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Jun 2018 20:03:54 +0200 Subject: [PATCH 010/155] Fix ping request not awaiting --- pyrogram/session/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 173e8846..1cd27c5e 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -324,7 +324,7 @@ class Session: break try: - self._send(functions.PingDelayDisconnect( + await self._send(functions.PingDelayDisconnect( 0, self.WAIT_TIMEOUT + 10 ), False) except (OSError, TimeoutError, Error): From 0b03612bc7c985d0af69f806447abf77dd84e9af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 13 Jun 2018 21:01:28 +0200 Subject: [PATCH 011/155] Make restart async --- pyrogram/session/session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 1cd27c5e..20d3c81a 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -207,9 +207,9 @@ class Session: log.debug("Session stopped") - def restart(self): - self.stop() - self.start() + async def restart(self): + await self.stop() + await self.start() def pack(self, message: Message): data = Long(self.current_salt.salt) + self.session_id + message.write() From 75121c9c57cc1ce96a282c00e721b22e68f56a79 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:18:38 +0200 Subject: [PATCH 012/155] Move MTProto related methods into a separate module --- pyrogram/crypto/__init__.py | 1 + pyrogram/crypto/mtproto.py | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 pyrogram/crypto/mtproto.py diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py index 08ed44f0..3112729d 100644 --- a/pyrogram/crypto/__init__.py +++ b/pyrogram/crypto/__init__.py @@ -18,5 +18,6 @@ from .aes import AES from .kdf import KDF +from .mtproto import MTProto from .prime import Prime from .rsa import RSA diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py new file mode 100644 index 00000000..d42caf7c --- /dev/null +++ b/pyrogram/crypto/mtproto.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from hashlib import sha256 +from io import BytesIO +from os import urandom + +from pyrogram.api.core import Message, Long +from . import AES, KDF + + +class MTProto: + INITIAL_SALT = 0x616e67656c696361 + + @staticmethod + def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes: + data = Long(salt) + session_id + message.write() + padding = urandom(-(len(data) + 12) % 16 + 12) + + # 88 = 88 + 0 (outgoing message) + msg_key_large = sha256(auth_key[88: 88 + 32] + data + padding).digest() + msg_key = msg_key_large[8:24] + aes_key, aes_iv = KDF(auth_key, msg_key, True) + + return auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) + + @staticmethod + def unpack(b: BytesIO, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message: + assert b.read(8) == auth_key_id, b.getvalue() + + msg_key = b.read(16) + aes_key, aes_iv = KDF(auth_key, msg_key, False) + data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) + + assert data.read(8) == Long(salt) or Long(salt) == Long(MTProto.INITIAL_SALT) + + # https://core.telegram.org/mtproto/security_guidelines#checking-session-id + assert data.read(8) == session_id + + message = Message.read(data) + + # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key + # https://core.telegram.org/mtproto/security_guidelines#checking-message-length + # 96 = 88 + 8 (incoming message) + assert msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] + + # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id + assert message.msg_id % 2 != 0 + + return message From 11ddf5f99d61dd5b623a6c68714d9d98378d7d8e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:22:52 +0200 Subject: [PATCH 013/155] Reorganize Session to make use of the MTProto module --- pyrogram/session/session.py | 91 +++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 39 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 20d3c81a..7b7942f9 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -19,20 +19,18 @@ import asyncio import logging import platform -import threading from datetime import datetime, timedelta -from hashlib import sha1, sha256 +from hashlib import sha1 from io import BytesIO -from os import urandom import pyrogram from pyrogram import __copyright__, __license__, __version__ from pyrogram.api import functions, types, core from pyrogram.api.all import layer -from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int +from pyrogram.api.core import Object, MsgContainer, Long, FutureSalt, Int from pyrogram.api.errors import Error, InternalServerError from pyrogram.connection import Connection -from pyrogram.crypto import AES, KDF +from pyrogram.crypto import MTProto from .internals import MsgId, MsgFactory, DataCenter log = logging.getLogger(__name__) @@ -58,7 +56,6 @@ class Session: platform.release() ) - INITIAL_SALT = 0x616e67656c696361 NET_WORKERS = 1 WAIT_TIMEOUT = 15 MAX_RETRIES = 5 @@ -137,7 +134,7 @@ class Session: self.net_worker_task = asyncio.ensure_future(self.net_worker()) self.recv_task = asyncio.ensure_future(self.recv()) - self.current_salt = FutureSalt(0, 0, self.INITIAL_SALT) + self.current_salt = FutureSalt(0, 0, MTProto.INITIAL_SALT) self.current_salt = FutureSalt(0, 0, (await self._send(functions.Ping(0))).new_server_salt) self.current_salt = (await self._send(functions.GetFutureSalts(1))).salts[0] @@ -215,36 +212,40 @@ class Session: data = Long(self.current_salt.salt) + self.session_id + message.write() padding = urandom(-(len(data) + 12) % 16 + 12) - # 88 = 88 + 0 (outgoing message) - msg_key_large = sha256(self.auth_key[88: 88 + 32] + data + padding).digest() - msg_key = msg_key_large[8:24] - aes_key, aes_iv = KDF(self.auth_key, msg_key, True) - - return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) - - def unpack(self, b: BytesIO) -> Message: - assert b.read(8) == self.auth_key_id, b.getvalue() - - msg_key = b.read(16) - aes_key, aes_iv = KDF(self.auth_key, msg_key, False) - data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) - data.read(8) - - # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - assert data.read(8) == self.session_id - - message = Message.read(data) - - # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key - # https://core.telegram.org/mtproto/security_guidelines#checking-message-length - # 96 = 88 + 8 (incoming message) - assert msg_key == sha256(self.auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] - - # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - # TODO: check for lower msg_ids - assert message.msg_id % 2 != 0 - - return message + # def pack(self, message: Message): + # data = Long(self.current_salt.salt) + self.session_id + message.write() + # padding = urandom(-(len(data) + 12) % 16 + 12) + # + # # 88 = 88 + 0 (outgoing message) + # msg_key_large = sha256(self.auth_key[88: 88 + 32] + data + padding).digest() + # msg_key = msg_key_large[8:24] + # aes_key, aes_iv = KDF(self.auth_key, msg_key, True) + # + # return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) + # + # def unpack(self, b: BytesIO) -> Message: + # assert b.read(8) == self.auth_key_id, b.getvalue() + # + # msg_key = b.read(16) + # aes_key, aes_iv = KDF(self.auth_key, msg_key, False) + # data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) + # data.read(8) + # + # # https://core.telegram.org/mtproto/security_guidelines#checking-session-id + # assert data.read(8) == self.session_id + # + # message = Message.read(data) + # + # # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key + # # https://core.telegram.org/mtproto/security_guidelines#checking-message-length + # # 96 = 88 + 8 (incoming message) + # assert msg_key == sha256(self.auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] + # + # # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id + # # TODO: check for lower msg_ids + # assert message.msg_id % 2 != 0 + # + # return message async def net_worker(self): name = threading.current_thread().name @@ -257,7 +258,13 @@ class Session: break try: - data = self.unpack(BytesIO(packet)) + data = MTProto.unpack( + BytesIO(packet), + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) messages = ( data.body.messages @@ -391,7 +398,13 @@ class Session: if wait_response: self.results[msg_id] = Result() - payload = self.pack(message) + payload = MTProto.pack( + message, + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) try: await self.connection.send(payload) From 2cf930bea08ed71392a84c0332800fd7352ea930 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:24:39 +0200 Subject: [PATCH 014/155] Remove commented MTProto methods --- pyrogram/session/session.py | 43 ++----------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 7b7942f9..ece4b662 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -205,47 +205,8 @@ class Session: log.debug("Session stopped") async def restart(self): - await self.stop() - await self.start() - - def pack(self, message: Message): - data = Long(self.current_salt.salt) + self.session_id + message.write() - padding = urandom(-(len(data) + 12) % 16 + 12) - - # def pack(self, message: Message): - # data = Long(self.current_salt.salt) + self.session_id + message.write() - # padding = urandom(-(len(data) + 12) % 16 + 12) - # - # # 88 = 88 + 0 (outgoing message) - # msg_key_large = sha256(self.auth_key[88: 88 + 32] + data + padding).digest() - # msg_key = msg_key_large[8:24] - # aes_key, aes_iv = KDF(self.auth_key, msg_key, True) - # - # return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) - # - # def unpack(self, b: BytesIO) -> Message: - # assert b.read(8) == self.auth_key_id, b.getvalue() - # - # msg_key = b.read(16) - # aes_key, aes_iv = KDF(self.auth_key, msg_key, False) - # data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) - # data.read(8) - # - # # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - # assert data.read(8) == self.session_id - # - # message = Message.read(data) - # - # # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key - # # https://core.telegram.org/mtproto/security_guidelines#checking-message-length - # # 96 = 88 + 8 (incoming message) - # assert msg_key == sha256(self.auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] - # - # # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - # # TODO: check for lower msg_ids - # assert message.msg_id % 2 != 0 - # - # return message + self.stop() + self.start() async def net_worker(self): name = threading.current_thread().name From 463ef828c25f5d1647d8071348bebf1a892b44fa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:25:15 +0200 Subject: [PATCH 015/155] Use put_nowait instead of put --- pyrogram/session/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ece4b662..ba224f46 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -259,7 +259,7 @@ class Session: msg_id = msg.body.msg_id else: if self.client is not None: - self.client.updates_queue.put(msg.body) + self.client.updates_queue.put_nowait(msg.body) if msg_id in self.results: self.results[msg_id].value = getattr(msg.body, "result", msg.body) From 68133e8be5cf63b6d90c8c38a3a4ff7954f67674 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:26:08 +0200 Subject: [PATCH 016/155] Better logs --- pyrogram/session/session.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ba224f46..ba0013cc 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -209,8 +209,7 @@ class Session: self.start() async def net_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) + log.info("NetWorkerTask started") while True: packet = await self.recv_queue.get() @@ -277,10 +276,10 @@ class Session: except Exception as e: log.error(e, exc_info=True) - log.debug("{} stopped".format(name)) + log.info("NetWorkerTask stopped") async def ping(self): - log.debug("Ping Task started") + log.info("PingTask started") while True: try: @@ -298,10 +297,10 @@ class Session: except (OSError, TimeoutError, Error): pass - log.debug("Ping Task stopped") + log.info("PingTask stopped") async def next_salt(self): - log.debug("NextSaltThread started") + log.info("NextSaltTask started") while True: now = datetime.now() @@ -331,10 +330,10 @@ class Session: self.connection.close() break - log.debug("NextSaltThread stopped") + log.info("NextSaltTask stopped") async def recv(self): - log.debug("Recv Task started") + log.info("RecvTask started") while True: packet = await self.connection.recv() @@ -350,7 +349,7 @@ class Session: self.recv_queue.put_nowait(packet) - log.debug("Recv Task stopped") + log.info("RecvTask stopped") async def _send(self, data: Object, wait_response: bool = True): message = self.msg_factory(data) From 775cbb568f2a1f4faee6abb85c69dbf82148b887 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 03:27:30 +0200 Subject: [PATCH 017/155] Small fixes --- pyrogram/session/session.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ba0013cc..30d4412d 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -286,8 +286,7 @@ class Session: await asyncio.wait_for(self.ping_task_event.wait(), self.PING_INTERVAL) except asyncio.TimeoutError: pass - - if self.ping_task_event.is_set(): + else: break try: @@ -320,8 +319,7 @@ class Session: await asyncio.wait_for(self.next_salt_task_event.wait(), dt) except asyncio.TimeoutError: pass - - if self.next_salt_task_event.is_set(): + else: break try: From b1f6131971db314a29930a452683f61cef27c39c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 13:04:52 +0200 Subject: [PATCH 018/155] Remove unused constant --- pyrogram/session/session.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 30d4412d..f16081df 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -56,7 +56,6 @@ class Session: platform.release() ) - NET_WORKERS = 1 WAIT_TIMEOUT = 15 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 From eeaf01654ba7aa22bc9ede8f6aca2011db7f8b2c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 13:05:22 +0200 Subject: [PATCH 019/155] Code style --- pyrogram/session/session.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index f16081df..2dfa0221 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -289,9 +289,11 @@ class Session: break try: - await self._send(functions.PingDelayDisconnect( - 0, self.WAIT_TIMEOUT + 10 - ), False) + await self._send( + functions.PingDelayDisconnect( + 0, self.WAIT_TIMEOUT + 10 + ), False + ) except (OSError, TimeoutError, Error): pass From d06e486c8ba13f373773d9f3f9f9af7055d0e705 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jun 2018 13:30:46 +0200 Subject: [PATCH 020/155] Reorganize imports --- pyrogram/session/session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 2dfa0221..b5def9a0 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -25,9 +25,9 @@ from io import BytesIO import pyrogram from pyrogram import __copyright__, __license__, __version__ -from pyrogram.api import functions, types, core +from pyrogram.api import functions, types from pyrogram.api.all import layer -from pyrogram.api.core import Object, MsgContainer, Long, FutureSalt, Int +from pyrogram.api.core import Object, MsgContainer, Int, Long, FutureSalt, FutureSalts from pyrogram.api.errors import Error, InternalServerError from pyrogram.connection import Connection from pyrogram.crypto import MTProto @@ -251,7 +251,7 @@ class Session: if isinstance(msg.body, (types.BadMsgNotification, types.BadServerSalt)): msg_id = msg.body.bad_msg_id - elif isinstance(msg.body, (core.FutureSalts, types.RpcResult)): + elif isinstance(msg.body, (FutureSalts, types.RpcResult)): msg_id = msg.body.req_msg_id elif isinstance(msg.body, types.Pong): msg_id = msg.body.msg_id From d1d789bf20c31b70980f4ccb95c15f4b9c1e98ff Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 15 Jun 2018 14:30:13 +0200 Subject: [PATCH 021/155] Fix restart not awaiting --- pyrogram/session/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b5def9a0..993ad2fa 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -204,8 +204,8 @@ class Session: log.debug("Session stopped") async def restart(self): - self.stop() - self.start() + await self.stop() + await self.start() async def net_worker(self): log.info("NetWorkerTask started") From 39b66b51d64a0fbfbcbca8244cf0a732f58063d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Jun 2018 22:05:54 +0200 Subject: [PATCH 022/155] Remove salt assertion --- pyrogram/crypto/mtproto.py | 5 ++--- pyrogram/session/session.py | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index d42caf7c..10839126 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -40,14 +40,13 @@ class MTProto: return auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) @staticmethod - def unpack(b: BytesIO, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message: + def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message: assert b.read(8) == auth_key_id, b.getvalue() msg_key = b.read(16) aes_key, aes_iv = KDF(auth_key, msg_key, False) data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) - - assert data.read(8) == Long(salt) or Long(salt) == Long(MTProto.INITIAL_SALT) + data.read(8) # https://core.telegram.org/mtproto/security_guidelines#checking-session-id assert data.read(8) == session_id diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 993ad2fa..c3dec02e 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -219,7 +219,6 @@ class Session: try: data = MTProto.unpack( BytesIO(packet), - self.current_salt.salt, self.session_id, self.auth_key, self.auth_key_id From 2b0746a140fcc7b2ea152ae5b5fe377b1f6f44b0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:33:23 +0200 Subject: [PATCH 023/155] Add timeout on recv loop --- pyrogram/session/session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index c3dec02e..60276947 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -334,7 +334,10 @@ class Session: log.info("RecvTask started") while True: - packet = await self.connection.recv() + try: + packet = await asyncio.wait_for(self.connection.recv(), self.connection.TIMEOUT) + except asyncio.TimeoutError: + packet = None if packet is None or len(packet) == 4: if packet: From 6da15b266d894e02a54ece61d27bedb54c88c228 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:34:10 +0200 Subject: [PATCH 024/155] Await tasks before stopping the session --- pyrogram/session/session.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 60276947..f06264c0 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -186,11 +186,11 @@ class Session: self.connection.close() - await self.recv_task + if self.recv_task: + await self.recv_task - self.recv_queue.put_nowait(None) - - await self.net_worker_task + if self.net_worker_task: + await self.net_worker_task for i in self.results.values(): i.event.set() From f983baf5cdc98d9baedd168769e7e19c99d230d1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:34:37 +0200 Subject: [PATCH 025/155] Add some more logs --- pyrogram/session/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index f06264c0..3a1fefd8 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -167,7 +167,7 @@ class Session: self.is_connected.set() - log.debug("Session started") + log.info("Session started") async def stop(self): self.is_connected.clear() @@ -201,7 +201,7 @@ class Session: except Exception as e: log.error(e, exc_info=True) - log.debug("Session stopped") + log.info("Session stopped") async def restart(self): await self.stop() From 57f917e6df19004550fa92cf3aefe6a2c4257f0b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:35:49 +0200 Subject: [PATCH 026/155] Don't print out the current salt --- pyrogram/session/session.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3a1fefd8..d58eae37 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -308,10 +308,8 @@ class Session: # 15 minutes before/after the current/next salt end/start time dt = (self.current_salt.valid_until - now).total_seconds() - 900 - log.debug("Current salt: {} | Next salt in {:.0f}m {:.0f}s ({})".format( - self.current_salt.salt, - dt // 60, - dt % 60, + log.info("Next salt in {:.0f}m {:.0f}s ({})".format( + dt // 60, dt % 60, now + timedelta(seconds=dt) )) @@ -340,6 +338,8 @@ class Session: packet = None if packet is None or len(packet) == 4: + self.recv_queue.put_nowait(None) + if packet: log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) From 0a6583a43cae82e7a7584b1352fd4a61b1d6a225 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:41:07 +0200 Subject: [PATCH 027/155] Turn the Dispatcher async --- pyrogram/client/dispatcher/dispatcher.py | 66 +++++++++++------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 51be2ebb..8efb6584 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -16,11 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import threading from collections import OrderedDict -from queue import Queue -from threading import Thread import pyrogram from pyrogram.api import types @@ -46,29 +44,17 @@ class Dispatcher: def __init__(self, client, workers): self.client = client self.workers = workers - self.workers_list = [] - self.updates = Queue() + + self.update_worker_task = None + self.updates = asyncio.Queue() self.groups = OrderedDict() - def start(self): - for i in range(self.workers): - self.workers_list.append( - Thread( - target=self.update_worker, - name="UpdateWorker#{}".format(i + 1) - ) - ) + async def start(self): + self.update_worker_task = asyncio.ensure_future(self.update_worker()) - self.workers_list[-1].start() - - def stop(self): - for _ in range(self.workers): - self.updates.put(None) - - for i in self.workers_list: - i.join() - - self.workers_list.clear() + async def stop(self): + self.updates.put_nowait(None) + await self.update_worker_task def add_handler(self, handler, group: int): if group not in self.groups: @@ -83,7 +69,9 @@ class Dispatcher: "Handler was not removed.".format(group)) self.groups[group].remove(handler) - def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False): + async def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False): + tasks = [] + for group in self.groups.values(): for handler in group: if is_raw: @@ -112,15 +100,17 @@ class Dispatcher: else: continue - handler.callback(*args) + tasks.append(handler.callback(*args)) break - def update_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) + await asyncio.gather(*tasks) + + async def update_worker(self): + log.info("UpdateWorkerTask started") while True: - update = self.updates.get() + tasks = [] + update = await self.updates.get() if update is None: break @@ -130,7 +120,7 @@ class Dispatcher: chats = {i.id: i for i in update[2]} update = update[0] - self.dispatch(update, users=users, chats=chats, is_raw=True) + tasks.append(self.dispatch(update, users=users, chats=chats, is_raw=True)) if isinstance(update, Dispatcher.MESSAGE_UPDATES): if isinstance(update.message, types.MessageEmpty): @@ -145,7 +135,7 @@ class Dispatcher: is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES) - self.dispatch( + tasks.append(self.dispatch( pyrogram.Update( message=((message if message.chat.type != "channel" else None) if not is_edited_message @@ -160,26 +150,28 @@ class Dispatcher: else None) if is_edited_message else None) ) - ) + )) elif isinstance(update, types.UpdateBotCallbackQuery): - self.dispatch( + tasks.append(self.dispatch( pyrogram.Update( callback_query=utils.parse_callback_query( self.client, update, users ) ) - ) + )) elif isinstance(update, types.UpdateInlineBotCallbackQuery): - self.dispatch( + tasks.append(self.dispatch( pyrogram.Update( callback_query=utils.parse_inline_callback_query( update, users ) ) - ) + )) else: continue + + await asyncio.gather(*tasks) except Exception as e: log.error(e, exc_info=True) - log.debug("{} stopped".format(name)) + log.info("UpdateWorkerTask stopped") From 52354b93d06e6085a79ccfe5ac9f57fded066c50 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:44:45 +0200 Subject: [PATCH 028/155] Add timeout when connecting --- pyrogram/connection/connection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index a9aba7c1..4a25b72e 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -25,6 +25,7 @@ log = logging.getLogger(__name__) class Connection: + TIMEOUT = 10 MAX_RETRIES = 3 MODES = { @@ -49,8 +50,8 @@ class Connection: try: log.info("Connecting...") - await self.protocol.connect(self.address) - except OSError: + await asyncio.wait_for(self.protocol.connect(self.address), Connection.TIMEOUT) + except (OSError, asyncio.TimeoutError): self.protocol.close() await asyncio.sleep(1) else: From 5d58ff2d941686e8da845b520e805783f572b867 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 18:45:08 +0200 Subject: [PATCH 029/155] Raise OSError in case "send" fails --- pyrogram/connection/connection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 4a25b72e..746dbf06 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -64,7 +64,10 @@ class Connection: log.info("Disconnected") async def send(self, data: bytes): - await self.protocol.send(data) + try: + await self.protocol.send(data) + except Exception: + raise OSError async def recv(self) -> bytes or None: return await self.protocol.recv() From b249062d2539c29a5a8fe8f5c3ec0a20243799b9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 19:17:56 +0200 Subject: [PATCH 030/155] Add a warning in case the connection failed --- pyrogram/connection/connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 746dbf06..c7210ad6 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -57,6 +57,7 @@ class Connection: else: break else: + log.warning("Connection failed! Trying again...") raise TimeoutError def close(self): From 1bc599e26ca7d905c6b6d1fd57881f3a991da02a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 17 Jun 2018 19:20:22 +0200 Subject: [PATCH 031/155] Delegate timeout to TCP --- pyrogram/connection/connection.py | 5 ++--- pyrogram/connection/transport/tcp/tcp.py | 10 ++++++++-- pyrogram/session/session.py | 5 +---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index c7210ad6..3e27638b 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -25,7 +25,6 @@ log = logging.getLogger(__name__) class Connection: - TIMEOUT = 10 MAX_RETRIES = 3 MODES = { @@ -50,8 +49,8 @@ class Connection: try: log.info("Connecting...") - await asyncio.wait_for(self.protocol.connect(self.address), Connection.TIMEOUT) - except (OSError, asyncio.TimeoutError): + await self.protocol.connect(self.address) + except OSError: self.protocol.close() await asyncio.sleep(1) else: diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index f006cadd..f541153e 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -34,6 +34,8 @@ log = logging.getLogger(__name__) class TCP: + TIMEOUT = 10 + def __init__(self, proxy: dict): self.proxy = proxy @@ -41,6 +43,7 @@ class TCP: self.reader = None # type: asyncio.StreamReader self.writer = None # type: asyncio.StreamWriter + self.socket.settimeout(TCP.TIMEOUT) self.proxy_enabled = proxy.get("enabled", False) if proxy and self.proxy_enabled: @@ -81,8 +84,11 @@ class TCP: while len(data) < length: try: - chunk = await self.reader.read(length - len(data)) - except OSError: + chunk = await asyncio.wait_for( + self.reader.read(length - len(data)), + TCP.TIMEOUT + ) + except (OSError, asyncio.TimeoutError): return None else: if chunk: diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index d58eae37..6479dfdd 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -332,10 +332,7 @@ class Session: log.info("RecvTask started") while True: - try: - packet = await asyncio.wait_for(self.connection.recv(), self.connection.TIMEOUT) - except asyncio.TimeoutError: - packet = None + packet = await self.connection.recv() if packet is None or len(packet) == 4: self.recv_queue.put_nowait(None) From 9a5ce0fe2d26b38a384dec90178d992a1b8e6e15 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 13:06:07 +0200 Subject: [PATCH 032/155] Clean up dispatcher and fix workers not being stopped correctly --- pyrogram/client/dispatcher/dispatcher.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 8efb6584..a77418c1 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -45,16 +45,28 @@ class Dispatcher: self.client = client self.workers = workers - self.update_worker_task = None + self.update_worker_tasks = [] self.updates = asyncio.Queue() self.groups = OrderedDict() async def start(self): - self.update_worker_task = asyncio.ensure_future(self.update_worker()) + for i in range(self.workers): + self.update_worker_tasks.append( + asyncio.ensure_future(self.update_worker()) + ) + + log.info("Started {} UpdateWorkerTasks".format(self.workers)) async def stop(self): - self.updates.put_nowait(None) - await self.update_worker_task + for i in range(self.workers): + self.updates.put_nowait(None) + + for i in self.update_worker_tasks: + await i + + self.update_worker_tasks.clear() + + log.info("Stopped {} UpdateWorkerTasks".format(self.workers)) def add_handler(self, handler, group: int): if group not in self.groups: @@ -106,8 +118,6 @@ class Dispatcher: await asyncio.gather(*tasks) async def update_worker(self): - log.info("UpdateWorkerTask started") - while True: tasks = [] update = await self.updates.get() @@ -173,5 +183,3 @@ class Dispatcher: await asyncio.gather(*tasks) except Exception as e: log.error(e, exc_info=True) - - log.info("UpdateWorkerTask stopped") From 8049c9129bfdf518d8716d846ec9f4faffb01953 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 13:07:02 +0200 Subject: [PATCH 033/155] Make Auth asynchronous --- pyrogram/session/auth.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 80956187..a41991fb 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -67,14 +67,14 @@ class Auth: b.seek(20) # Skip auth_key_id (8), message_id (8) and message_length (4) return Object.read(b) - def send(self, data: Object): + async def send(self, data: Object): data = self.pack(data) - self.connection.send(data) - response = BytesIO(self.connection.recv()) + await self.connection.send(data) + response = BytesIO(await self.connection.recv()) return self.unpack(response) - def create(self): + async def create(self): """ https://core.telegram.org/mtproto/auth_key https://core.telegram.org/mtproto/samples-auth_key @@ -89,12 +89,12 @@ class Auth: try: log.info("Start creating a new auth key on DC{}".format(self.dc_id)) - self.connection.connect() + await self.connection.connect() # Step 1; Step 2 nonce = int.from_bytes(urandom(16), "little", signed=True) log.debug("Send req_pq: {}".format(nonce)) - res_pq = self.send(functions.ReqPqMulti(nonce)) + res_pq = await self.send(functions.ReqPqMulti(nonce)) log.debug("Got ResPq: {}".format(res_pq.server_nonce)) log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints)) @@ -138,7 +138,7 @@ class Auth: # Step 5. TODO: Handle "server_DH_params_fail". Code assumes response is ok log.debug("Send req_DH_params") - server_dh_params = self.send( + server_dh_params = await self.send( functions.ReqDHParams( nonce, server_nonce, @@ -198,7 +198,7 @@ class Auth: encrypted_data = AES.ige256_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv) log.debug("Send set_client_DH_params") - set_client_dh_params_answer = self.send( + set_client_dh_params_answer = await self.send( functions.SetClientDHParams( nonce, server_nonce, From e3a667a8fead262acd57a1265ed3564829ebc3d9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 21:11:28 +0200 Subject: [PATCH 034/155] Make Syncer asynchronous (lol) --- pyrogram/client/ext/syncer.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 125c5ce0..66d28da1 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -16,13 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import base64 import json import logging import os import shutil import time -from threading import Thread, Event, Lock from . import utils @@ -33,13 +33,12 @@ class Syncer: INTERVAL = 20 clients = {} - thread = None - event = Event() - lock = Lock() + event = asyncio.Event() + lock = asyncio.Lock() @classmethod - def add(cls, client): - with cls.lock: + async def add(cls, client): + with await cls.lock: cls.sync(client) cls.clients[id(client)] = client @@ -48,8 +47,8 @@ class Syncer: cls.start() @classmethod - def remove(cls, client): - with cls.lock: + async def remove(cls, client): + with await cls.lock: cls.sync(client) del cls.clients[id(client)] @@ -60,25 +59,24 @@ class Syncer: @classmethod def start(cls): cls.event.clear() - cls.thread = Thread(target=cls.worker, name=cls.__name__) - cls.thread.start() + asyncio.ensure_future(cls.worker()) @classmethod def stop(cls): cls.event.set() @classmethod - def worker(cls): + async def worker(cls): while True: - cls.event.wait(cls.INTERVAL) - - if cls.event.is_set(): + try: + await asyncio.wait_for(cls.event.wait(), cls.INTERVAL) + except asyncio.TimeoutError: + with await cls.lock: + for client in cls.clients.values(): + cls.sync(client) + else: break - with cls.lock: - for client in cls.clients.values(): - cls.sync(client) - @classmethod def sync(cls, client): temporary = os.path.join(client.workdir, "{}.sync".format(client.session_name)) From 09dd7155562dc24841b78fdf443c1d35f99fabba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 21:12:04 +0200 Subject: [PATCH 035/155] Small tweaks --- pyrogram/client/dispatcher/dispatcher.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index a77418c1..79480dfb 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -119,7 +119,6 @@ class Dispatcher: async def update_worker(self): while True: - tasks = [] update = await self.updates.get() if update is None: @@ -130,13 +129,13 @@ class Dispatcher: chats = {i.id: i for i in update[2]} update = update[0] - tasks.append(self.dispatch(update, users=users, chats=chats, is_raw=True)) + await self.dispatch(update, users=users, chats=chats, is_raw=True) if isinstance(update, Dispatcher.MESSAGE_UPDATES): if isinstance(update.message, types.MessageEmpty): continue - message = utils.parse_messages( + message = await utils.parse_messages( self.client, update.message, users, @@ -145,7 +144,7 @@ class Dispatcher: is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES) - tasks.append(self.dispatch( + await self.dispatch( pyrogram.Update( message=((message if message.chat.type != "channel" else None) if not is_edited_message @@ -160,26 +159,24 @@ class Dispatcher: else None) if is_edited_message else None) ) - )) + ) elif isinstance(update, types.UpdateBotCallbackQuery): - tasks.append(self.dispatch( + await self.dispatch( pyrogram.Update( - callback_query=utils.parse_callback_query( + callback_query=await utils.parse_callback_query( self.client, update, users ) ) - )) + ) elif isinstance(update, types.UpdateInlineBotCallbackQuery): - tasks.append(self.dispatch( + await self.dispatch( pyrogram.Update( - callback_query=utils.parse_inline_callback_query( + callback_query=await utils.parse_inline_callback_query( update, users ) ) - )) + ) else: continue - - await asyncio.gather(*tasks) except Exception as e: log.error(e, exc_info=True) From 26e828b9566ed3ba13b47705c0df488514ca91e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 21:21:26 +0200 Subject: [PATCH 036/155] Make BaseClient asynchronous and default DOWNLOAD_WORKERS to 4 --- pyrogram/client/ext/base_client.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 9c0fb26b..04a6ac12 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -16,9 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import re -from queue import Queue -from threading import Lock from ..style import Markdown, HTML from ...api.core import Object @@ -30,7 +29,7 @@ class BaseClient: BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") DIALOGS_AT_ONCE = 100 UPDATES_WORKERS = 1 - DOWNLOAD_WORKERS = 1 + DOWNLOAD_WORKERS = 4 OFFLINE_SLEEP = 300 MEDIA_TYPE_ID = { @@ -65,15 +64,15 @@ class BaseClient: self.session = None self.media_sessions = {} - self.media_sessions_lock = Lock() + self.media_sessions_lock = asyncio.Lock() self.is_started = None self.is_idle = None - self.updates_queue = Queue() - self.updates_workers_list = [] - self.download_queue = Queue() - self.download_workers_list = [] + self.updates_queue = asyncio.Queue() + self.updates_worker_task = None + self.download_queue = asyncio.Queue() + self.download_worker_tasks = [] self.disconnect_handler = None From 21af0f3e821c244ca779040795fb22f75f466962 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 21:22:33 +0200 Subject: [PATCH 037/155] More async chore --- pyrogram/client/ext/utils.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index d7a09ee1..bb4efb4c 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -249,7 +249,7 @@ def encode(s: bytes) -> str: # TODO: Reorganize code, maybe split parts as well -def parse_messages( +async def parse_messages( client, messages: list or types.Message or types.MessageService or types.MessageEmpty, users: dict, @@ -484,9 +484,9 @@ def parse_messages( if isinstance(sticker_attribute.stickerset, types.InputStickerSetID): try: - set_name = client.send( + set_name = (await client.send( functions.messages.GetStickerSet(sticker_attribute.stickerset) - ).set.short_name + )).set.short_name except StickersetInvalid: set_name = None else: @@ -591,7 +591,7 @@ def parse_messages( if message.reply_to_msg_id and replies: while True: try: - m.reply_to_message = client.get_messages( + m.reply_to_message = await client.get_messages( m.chat.id, message.reply_to_msg_id, replies=replies - 1 ) @@ -693,7 +693,7 @@ def parse_messages( if isinstance(action, types.MessageActionPinMessage): while True: try: - m.pinned_message = client.get_messages( + m.pinned_message = await client.get_messages( m.chat.id, message.reply_to_msg_id, replies=0 ) @@ -790,7 +790,7 @@ def parse_photos(photos): ) -def parse_callback_query(client, callback_query, users): +async def parse_callback_query(client, callback_query, users): peer = callback_query.peer if isinstance(peer, types.PeerUser): @@ -803,14 +803,14 @@ def parse_callback_query(client, callback_query, users): return pyrogram_types.CallbackQuery( id=str(callback_query.query_id), from_user=parse_user(users[callback_query.user_id]), - message=client.get_messages(peer_id, callback_query.msg_id), + message=await client.get_messages(peer_id, callback_query.msg_id), chat_instance=str(callback_query.chat_instance), data=callback_query.data.decode(), game_short_name=callback_query.game_short_name ) -def parse_inline_callback_query(callback_query, users): +async def parse_inline_callback_query(callback_query, users): return pyrogram_types.CallbackQuery( id=str(callback_query.query_id), from_user=parse_user(users[callback_query.user_id]), @@ -828,7 +828,7 @@ def parse_inline_callback_query(callback_query, users): ) -def parse_chat_full( +async def parse_chat_full( client, chat_full: types.messages.ChatFull or types.UserFull ) -> pyrogram_types.Chat: @@ -853,7 +853,7 @@ def parse_chat_full( chat.sticker_set_name = full_chat.stickerset if full_chat.pinned_msg_id: - chat.pinned_message = client.get_messages( + chat.pinned_message = await client.get_messages( int("-100" + str(full_chat.id)), full_chat.pinned_msg_id ) From 4d72f84991e27f0678e1ef428b2568f980abd36d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jun 2018 21:30:13 +0200 Subject: [PATCH 038/155] Even more async chore --- .../callback_query/answer_callback_query.py | 14 +++---- .../bots/inline/get_inline_bot_results.py | 16 ++++---- .../bots/inline/send_inline_bot_result.py | 16 ++++---- .../methods/chats/export_chat_invite_link.py | 8 ++-- pyrogram/client/methods/chats/get_chat.py | 12 +++--- pyrogram/client/methods/chats/join_chat.py | 8 ++-- .../client/methods/chats/kick_chat_member.py | 16 ++++---- pyrogram/client/methods/chats/leave_chat.py | 12 +++--- .../methods/chats/promote_chat_member.py | 28 ++++++------- .../methods/chats/restrict_chat_member.py | 22 +++++----- .../client/methods/chats/unban_chat_member.py | 12 +++--- .../client/methods/contacts/add_contacts.py | 4 +- .../methods/contacts/delete_contacts.py | 6 +-- .../client/methods/contacts/get_contacts.py | 8 ++-- pyrogram/client/methods/download_media.py | 20 +++++----- .../messages/action/send_chat_action.py | 12 +++--- .../methods/messages/forward_messages.py | 18 ++++----- .../client/methods/messages/get_history.py | 20 +++++----- .../client/methods/messages/get_messages.py | 14 +++---- .../methods/messages/media/send_audio.py | 36 ++++++++--------- .../methods/messages/media/send_contact.py | 22 +++++----- .../methods/messages/media/send_document.py | 30 +++++++------- .../client/methods/messages/media/send_gif.py | 14 +++---- .../methods/messages/media/send_location.py | 20 +++++----- .../messages/media/send_media_group.py | 26 ++++++------ .../methods/messages/media/send_photo.py | 32 +++++++-------- .../methods/messages/media/send_sticker.py | 26 ++++++------ .../methods/messages/media/send_venue.py | 26 ++++++------ .../methods/messages/media/send_video.py | 40 +++++++++---------- .../methods/messages/media/send_video_note.py | 30 +++++++------- .../methods/messages/media/send_voice.py | 32 +++++++-------- .../client/methods/messages/send_message.py | 22 +++++----- .../messages/update/delete_messages.py | 14 +++---- .../messages/update/edit_message_caption.py | 18 ++++----- .../update/edit_message_reply_markup.py | 14 +++---- .../messages/update/edit_message_text.py | 20 +++++----- 36 files changed, 344 insertions(+), 344 deletions(-) diff --git a/pyrogram/client/methods/bots/callback_query/answer_callback_query.py b/pyrogram/client/methods/bots/callback_query/answer_callback_query.py index a4baa166..be6fbc0c 100644 --- a/pyrogram/client/methods/bots/callback_query/answer_callback_query.py +++ b/pyrogram/client/methods/bots/callback_query/answer_callback_query.py @@ -21,12 +21,12 @@ from ....ext import BaseClient class AnswerCallbackQuery(BaseClient): - def answer_callback_query(self, - callback_query_id: str, - text: str = None, - show_alert: bool = None, - url: str = None, - cache_time: int = 0): + async def answer_callback_query(self, + callback_query_id: str, + text: str = None, + show_alert: bool = None, + url: str = None, + cache_time: int = 0): """Use this method to send answers to callback queries sent from inline keyboards. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. @@ -51,7 +51,7 @@ class AnswerCallbackQuery(BaseClient): The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. """ - return self.send( + return await self.send( functions.messages.SetBotCallbackAnswer( query_id=int(callback_query_id), cache_time=cache_time, diff --git a/pyrogram/client/methods/bots/inline/get_inline_bot_results.py b/pyrogram/client/methods/bots/inline/get_inline_bot_results.py index 52c3b005..86ab18b5 100644 --- a/pyrogram/client/methods/bots/inline/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/inline/get_inline_bot_results.py @@ -22,12 +22,12 @@ from ....ext import BaseClient class GetInlineBotResults(BaseClient): - def get_inline_bot_results(self, - bot: int or str, - query: str, - offset: str = "", - latitude: float = None, - longitude: float = None): + async def get_inline_bot_results(self, + bot: int or str, + query: str, + offset: str = "", + latitude: float = None, + longitude: float = None): """Use this method to get bot results via inline queries. You can then send a result using :obj:`send_inline_bot_result ` @@ -60,9 +60,9 @@ class GetInlineBotResults(BaseClient): # TODO: Don't return the raw type try: - return self.send( + return await self.send( functions.messages.GetInlineBotResults( - bot=self.resolve_peer(bot), + bot=await self.resolve_peer(bot), peer=types.InputPeerSelf(), query=query, offset=offset, diff --git a/pyrogram/client/methods/bots/inline/send_inline_bot_result.py b/pyrogram/client/methods/bots/inline/send_inline_bot_result.py index 947433cd..3ce58cd8 100644 --- a/pyrogram/client/methods/bots/inline/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/inline/send_inline_bot_result.py @@ -21,12 +21,12 @@ from ....ext import BaseClient class SendInlineBotResult(BaseClient): - def send_inline_bot_result(self, - chat_id: int or str, - query_id: int, - result_id: str, - disable_notification: bool = None, - reply_to_message_id: int = None): + async def send_inline_bot_result(self, + chat_id: int or str, + query_id: int, + result_id: str, + disable_notification: bool = None, + reply_to_message_id: int = None): """Use this method to send an inline bot result. Bot results can be retrieved using :obj:`get_inline_bot_results ` @@ -56,9 +56,9 @@ class SendInlineBotResult(BaseClient): Raises: :class:`Error ` """ - return self.send( + return await self.send( functions.messages.SendInlineBotResult( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), query_id=query_id, id=result_id, random_id=self.rnd_id(), diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index dc289af3..26febf1d 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class ExportChatInviteLink(BaseClient): - def export_chat_invite_link(self, chat_id: int or str): + async def export_chat_invite_link(self, chat_id: int or str): """Use this method to generate a new invite link for a chat; any previously generated link is revoked. You must be an administrator in the chat for this to work and have the appropriate admin rights. @@ -37,16 +37,16 @@ class ExportChatInviteLink(BaseClient): Raises: :class:`Error ` """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - return self.send( + return await self.send( functions.messages.ExportChatInvite( chat_id=peer.chat_id ) ).link elif isinstance(peer, types.InputPeerChannel): - return self.send( + return await self.send( functions.channels.ExportInvite( channel=peer ) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 194e6171..9b5a5fe8 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -21,7 +21,7 @@ from ...ext import BaseClient, utils class GetChat(BaseClient): - def get_chat(self, chat_id: int or str): + async def get_chat(self, chat_id: int or str): """Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) @@ -31,13 +31,13 @@ class GetChat(BaseClient): Raises: :class:`Error ` """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - r = self.send(functions.channels.GetFullChannel(peer)) + r = await self.send(functions.channels.GetFullChannel(peer)) elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): - r = self.send(functions.users.GetFullUser(peer)) + r = await self.send(functions.users.GetFullUser(peer)) else: - r = self.send(functions.messages.GetFullChat(peer.chat_id)) + r = await self.send(functions.messages.GetFullChat(peer.chat_id)) - return utils.parse_chat_full(self, r) + return await utils.parse_chat_full(self, r) diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index b7b8d42c..75f8033f 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class JoinChat(BaseClient): - def join_chat(self, chat_id: str): + async def join_chat(self, chat_id: str): """Use this method to join a group chat or channel. Args: @@ -35,13 +35,13 @@ class JoinChat(BaseClient): match = self.INVITE_LINK_RE.match(chat_id) if match: - return self.send( + return await self.send( functions.messages.ImportChatInvite( hash=match.group(1) ) ) else: - resolved_peer = self.send( + resolved_peer = await self.send( functions.contacts.ResolveUsername( username=chat_id.lower().strip("@") ) @@ -52,7 +52,7 @@ class JoinChat(BaseClient): access_hash=resolved_peer.chats[0].access_hash ) - return self.send( + return await self.send( functions.channels.JoinChannel( channel=channel ) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 6275718c..5b8dda53 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -21,10 +21,10 @@ from ...ext import BaseClient class KickChatMember(BaseClient): - def kick_chat_member(self, - chat_id: int or str, - user_id: int or str, - until_date: int = 0): + async def kick_chat_member(self, + chat_id: int or str, + user_id: int or str, + until_date: int = 0): """Use this method to kick a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must @@ -55,11 +55,11 @@ class KickChatMember(BaseClient): Raises: :class:`Error ` """ - chat_peer = self.resolve_peer(chat_id) - user_peer = self.resolve_peer(user_id) + chat_peer = await self.resolve_peer(chat_id) + user_peer = await self.resolve_peer(user_id) if isinstance(chat_peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.EditBanned( channel=chat_peer, user_id=user_peer, @@ -77,7 +77,7 @@ class KickChatMember(BaseClient): ) ) else: - self.send( + await self.send( functions.messages.DeleteChatUser( chat_id=abs(chat_id), user_id=user_peer diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 55d6ef21..9d7dfcef 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class LeaveChat(BaseClient): - def leave_chat(self, chat_id: int or str, delete: bool = False): + async def leave_chat(self, chat_id: int or str, delete: bool = False): """Use this method to leave a group chat or channel. Args: @@ -35,16 +35,16 @@ class LeaveChat(BaseClient): Raises: :class:`Error ` """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - return self.send( + return await self.send( functions.channels.LeaveChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) elif isinstance(peer, types.InputPeerChat): - r = self.send( + r = await self.send( functions.messages.DeleteChatUser( chat_id=peer.chat_id, user_id=types.InputPeerSelf() @@ -52,7 +52,7 @@ class LeaveChat(BaseClient): ) if delete: - self.send( + await self.send( functions.messages.DeleteHistory( peer=peer, max_id=0 diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index eb70578a..9cfe426b 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -21,17 +21,17 @@ from ...ext import BaseClient class PromoteChatMember(BaseClient): - def promote_chat_member(self, - chat_id: int or str, - user_id: int or str, - can_change_info: bool = True, - can_post_messages: bool = True, - can_edit_messages: bool = True, - can_delete_messages: bool = True, - can_invite_users: bool = True, - can_restrict_members: bool = True, - can_pin_messages: bool = True, - can_promote_members: bool = False): + async def promote_chat_member(self, + chat_id: int or str, + user_id: int or str, + can_change_info: bool = True, + can_post_messages: bool = True, + can_edit_messages: bool = True, + can_delete_messages: bool = True, + can_invite_users: bool = True, + can_restrict_members: bool = True, + can_pin_messages: bool = True, + can_promote_members: bool = False): """Use this method to promote or demote a user in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass False for all boolean parameters to demote a user. @@ -77,10 +77,10 @@ class PromoteChatMember(BaseClient): Raises: :class:`Error ` """ - self.send( + await self.send( functions.channels.EditAdmin( - channel=self.resolve_peer(chat_id), - user_id=self.resolve_peer(user_id), + channel=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), admin_rights=types.ChannelAdminRights( change_info=can_change_info or None, post_messages=can_post_messages or None, diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index ae1e4d9c..a9439ed1 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -21,14 +21,14 @@ from ...ext import BaseClient class RestrictChatMember(BaseClient): - def restrict_chat_member(self, - chat_id: int or str, - user_id: int or str, - until_date: int = 0, - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False): + async def restrict_chat_member(self, + chat_id: int or str, + user_id: int or str, + until_date: int = 0, + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False): """Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift restrictions from a user. @@ -93,10 +93,10 @@ class RestrictChatMember(BaseClient): send_media = None embed_links = None - self.send( + await self.send( functions.channels.EditBanned( - channel=self.resolve_peer(chat_id), - user_id=self.resolve_peer(user_id), + channel=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), banned_rights=types.ChannelBannedRights( until_date=until_date, send_messages=send_messages, diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index b0916eb4..ed00f428 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -21,9 +21,9 @@ from ...ext import BaseClient class UnbanChatMember(BaseClient): - def unban_chat_member(self, - chat_id: int or str, - user_id: int or str): + async def unban_chat_member(self, + chat_id: int or str, + user_id: int or str): """Use this method to unban a previously kicked user in a supergroup or channel. The user will **not** return to the group or channel automatically, but will be able to join via link, etc. You must be an administrator for this to work. @@ -43,10 +43,10 @@ class UnbanChatMember(BaseClient): Raises: :class:`Error ` """ - self.send( + await self.send( functions.channels.EditBanned( - channel=self.resolve_peer(chat_id), - user_id=self.resolve_peer(user_id), + channel=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), banned_rights=types.ChannelBannedRights( until_date=0 ) diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index 10b5e415..75f4f8a8 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class AddContacts(BaseClient): - def add_contacts(self, contacts: list): + async def add_contacts(self, contacts: list): """Use this method to add contacts to your Telegram address book. Args: @@ -34,7 +34,7 @@ class AddContacts(BaseClient): Raises: :class:`Error ` """ - imported_contacts = self.send( + imported_contacts = await self.send( functions.contacts.ImportContacts( contacts=contacts ) diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py index ed3d67f9..ef133d8c 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class DeleteContacts(BaseClient): - def delete_contacts(self, ids: list): + async def delete_contacts(self, ids: list): """Use this method to delete contacts from your Telegram address book Args: @@ -40,14 +40,14 @@ class DeleteContacts(BaseClient): for i in ids: try: - input_user = self.resolve_peer(i) + input_user = await self.resolve_peer(i) except PeerIdInvalid: continue else: if isinstance(input_user, types.InputPeerUser): contacts.append(input_user) - return self.send( + return await self.send( functions.contacts.DeleteContacts( id=contacts ) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 376e8be2..b73a1f2c 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import time from pyrogram.api import functions, types from pyrogram.api.errors import FloodWait @@ -27,7 +27,7 @@ log = logging.getLogger(__name__) class GetContacts(BaseClient): - def get_contacts(self): + async def get_contacts(self): """Use this method to get contacts from your Telegram address book Requires no parameters. @@ -40,10 +40,10 @@ class GetContacts(BaseClient): """ while True: try: - contacts = self.send(functions.contacts.GetContacts(0)) + contacts = await self.send(functions.contacts.GetContacts(0)) except FloodWait as e: log.warning("get_contacts flood: waiting {} seconds".format(e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) continue else: if isinstance(contacts, types.contacts.Contacts): diff --git a/pyrogram/client/methods/download_media.py b/pyrogram/client/methods/download_media.py index 5eb04fbc..56a89472 100644 --- a/pyrogram/client/methods/download_media.py +++ b/pyrogram/client/methods/download_media.py @@ -16,19 +16,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from threading import Event +import asyncio from pyrogram.client import types as pyrogram_types from ..ext import BaseClient class DownloadMedia(BaseClient): - def download_media(self, - message: pyrogram_types.Message or str, - file_name: str = "", - block: bool = True, - progress: callable = None, - progress_args: tuple = None): + async def download_media(self, + message: pyrogram_types.Message or str, + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = None): """Use this method to download the media from a Message. Args: @@ -114,12 +114,12 @@ class DownloadMedia(BaseClient): else: return - done = Event() + done = asyncio.Event() path = [None] - self.download_queue.put((media, file_name, done, progress, progress_args, path)) + self.download_queue.put_nowait((media, file_name, done, progress, progress_args, path)) if block: - done.wait() + await done.wait() return path[0] diff --git a/pyrogram/client/methods/messages/action/send_chat_action.py b/pyrogram/client/methods/messages/action/send_chat_action.py index 4b34dd40..b770f60e 100644 --- a/pyrogram/client/methods/messages/action/send_chat_action.py +++ b/pyrogram/client/methods/messages/action/send_chat_action.py @@ -21,10 +21,10 @@ from ....ext import BaseClient, ChatAction class SendChatAction(BaseClient): - def send_chat_action(self, - chat_id: int or str, - action: ChatAction or str, - progress: int = 0): + async def send_chat_action(self, + chat_id: int or str, + action: ChatAction or str, + progress: int = 0): """Use this method when you need to tell the other party that something is happening on your side. Args: @@ -63,9 +63,9 @@ class SendChatAction(BaseClient): else: action = action() - return self.send( + return await self.send( functions.messages.SetTyping( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), action=action ) ) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 606e54b5..03ca9487 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -21,11 +21,11 @@ from ...ext import BaseClient, utils class ForwardMessages(BaseClient): - def forward_messages(self, - chat_id: int or str, - from_chat_id: int or str, - message_ids, - disable_notification: bool = None): + async def forward_messages(self, + chat_id: int or str, + from_chat_id: int or str, + message_ids, + disable_notification: bool = None): """Use this method to forward messages of any kind. Args: @@ -61,10 +61,10 @@ class ForwardMessages(BaseClient): is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] - r = self.send( + r = await self.send( functions.messages.ForwardMessages( - to_peer=self.resolve_peer(chat_id), - from_peer=self.resolve_peer(from_chat_id), + to_peer=await self.resolve_peer(chat_id), + from_peer=await self.resolve_peer(from_chat_id), id=message_ids, silent=disable_notification or None, random_id=[self.rnd_id() for _ in message_ids] @@ -79,7 +79,7 @@ class ForwardMessages(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): messages.append( - utils.parse_messages( + await utils.parse_messages( self, i.message, users, chats ) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 4089dde9..d6c6479a 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -22,12 +22,12 @@ from ...ext import BaseClient, utils class GetHistory(BaseClient): - def get_history(self, - chat_id: int or str, - offset: int = 0, - limit: int = 100, - offset_id: int = 0, - offset_date: int = 0): + async def get_history(self, + chat_id: int or str, + offset: int = 0, + limit: int = 100, + offset_id: int = 0, + offset_date: int = 0): """Use this method to retrieve the history of a chat. You can get up to 100 messages at once. @@ -60,9 +60,9 @@ class GetHistory(BaseClient): :class:`Error ` """ - r = self.send( + r = await self.send( functions.messages.GetHistory( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, add_offset=offset, @@ -83,7 +83,7 @@ class GetHistory(BaseClient): } if reply_to_messages: - temp = self.get_messages( + temp = await self.get_messages( chat_id, reply_to_messages, replies=0 ) @@ -93,7 +93,7 @@ class GetHistory(BaseClient): for i in range(len(temp)): reply_to_messages[temp[i].message_id] = temp[i] - messages = utils.parse_messages( + messages = await utils.parse_messages( self, r.messages, users, chats, replies=0 diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 49535a40..54c11830 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -21,10 +21,10 @@ from ...ext import BaseClient, utils class GetMessages(BaseClient): - def get_messages(self, - chat_id: int or str, - message_ids, - replies: int = 1): + async def get_messages(self, + chat_id: int or str, + message_ids, + replies: int = 1): """Use this method to get messages that belong to a specific chat. You can retrieve up to 200 messages at once. @@ -51,7 +51,7 @@ class GetMessages(BaseClient): Raises: :class:`Error ` """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] message_ids = [types.InputMessageID(i) for i in message_ids] @@ -66,9 +66,9 @@ class GetMessages(BaseClient): id=message_ids ) - r = self.send(rpc) + r = await self.send(rpc) - messages = utils.parse_messages( + messages = await utils.parse_messages( self, r.messages, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/media/send_audio.py b/pyrogram/client/methods/messages/media/send_audio.py index 41f4457f..00ccbe4d 100644 --- a/pyrogram/client/methods/messages/media/send_audio.py +++ b/pyrogram/client/methods/messages/media/send_audio.py @@ -27,19 +27,19 @@ from ....ext import BaseClient, utils class SendAudio(BaseClient): - def send_audio(self, - chat_id: int or str, - audio: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - performer: str = None, - title: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_audio(self, + chat_id: int or str, + audio: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: str = None, + title: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send audio files. For sending voice messages, use the :obj:`send_voice()` method instead. @@ -118,7 +118,7 @@ class SendAudio(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(audio): - file = self.save_file(audio, progress=progress, progress_args=progress_args) + file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), file=file, @@ -160,9 +160,9 @@ class SendAudio(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -172,11 +172,11 @@ class SendAudio(BaseClient): ) ) except FilePartMissing as e: - self.save_file(audio, file_id=file.id, file_part=e.x) + await self.save_file(audio, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_contact.py b/pyrogram/client/methods/messages/media/send_contact.py index eb1bb6c4..2965fb5a 100644 --- a/pyrogram/client/methods/messages/media/send_contact.py +++ b/pyrogram/client/methods/messages/media/send_contact.py @@ -21,14 +21,14 @@ from ....ext import BaseClient, utils class SendContact(BaseClient): - def send_contact(self, - chat_id: int or str, - phone_number: str, - first_name: str, - last_name: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + async def send_contact(self, + chat_id: int or str, + phone_number: str, + first_name: str, + last_name: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): """Use this method to send phone contacts. Args: @@ -64,9 +64,9 @@ class SendContact(BaseClient): Raises: :class:`Error ` """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaContact( phone_number, first_name, @@ -82,7 +82,7 @@ class SendContact(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_document.py b/pyrogram/client/methods/messages/media/send_document.py index 1092147f..f32f78c6 100644 --- a/pyrogram/client/methods/messages/media/send_document.py +++ b/pyrogram/client/methods/messages/media/send_document.py @@ -27,16 +27,16 @@ from ....ext import BaseClient, utils class SendDocument(BaseClient): - def send_document(self, - chat_id: int or str, - document: str, - caption: str = "", - parse_mode: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_document(self, + chat_id: int or str, + document: str, + caption: str = "", + parse_mode: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send general files. Args: @@ -104,7 +104,7 @@ class SendDocument(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(document): - file = self.save_file(document, progress=progress, progress_args=progress_args) + file = await self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), file=file, @@ -141,9 +141,9 @@ class SendDocument(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -153,11 +153,11 @@ class SendDocument(BaseClient): ) ) except FilePartMissing as e: - self.save_file(document, file_id=file.id, file_part=e.x) + await self.save_file(document, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_gif.py b/pyrogram/client/methods/messages/media/send_gif.py index 0d4bb4b9..bdda234e 100644 --- a/pyrogram/client/methods/messages/media/send_gif.py +++ b/pyrogram/client/methods/messages/media/send_gif.py @@ -27,7 +27,7 @@ from ....ext import BaseClient, utils class SendGIF(BaseClient): - def send_gif(self, + async def send_gif(self, chat_id: int or str, gif: str, caption: str = "", @@ -122,8 +122,8 @@ class SendGIF(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(gif): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(gif, progress=progress, progress_args=progress_args) + thumb = None if thumb is None else await self.save_file(thumb) + file = await self.save_file(gif, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], file=file, @@ -168,9 +168,9 @@ class SendGIF(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -180,11 +180,11 @@ class SendGIF(BaseClient): ) ) except FilePartMissing as e: - self.save_file(gif, file_id=file.id, file_part=e.x) + await self.save_file(gif, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_location.py b/pyrogram/client/methods/messages/media/send_location.py index 08dac02b..0a1a1776 100644 --- a/pyrogram/client/methods/messages/media/send_location.py +++ b/pyrogram/client/methods/messages/media/send_location.py @@ -21,13 +21,13 @@ from ....ext import BaseClient, utils class SendLocation(BaseClient): - def send_location(self, - chat_id: int or str, - latitude: float, - longitude: float, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + async def send_location(self, + chat_id: int or str, + latitude: float, + longitude: float, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): """Use this method to send points on the map. Args: @@ -60,9 +60,9 @@ class SendLocation(BaseClient): Raises: :class:`Error ` """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaGeoPoint( types.InputGeoPoint( latitude, @@ -79,7 +79,7 @@ class SendLocation(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_media_group.py b/pyrogram/client/methods/messages/media/send_media_group.py index 6d004d9f..f0af01be 100644 --- a/pyrogram/client/methods/messages/media/send_media_group.py +++ b/pyrogram/client/methods/messages/media/send_media_group.py @@ -31,11 +31,11 @@ class SendMediaGroup(BaseClient): # TODO: Add progress parameter # TODO: Return new Message object # TODO: Figure out how to send albums using URLs - def send_media_group(self, - chat_id: int or str, - media: list, - disable_notification: bool = None, - reply_to_message_id: int = None): + async def send_media_group(self, + chat_id: int or str, + media: list, + disable_notification: bool = None, + reply_to_message_id: int = None): """Use this method to send a group of photos or videos as an album. On success, an Update containing the sent Messages is returned. @@ -65,11 +65,11 @@ class SendMediaGroup(BaseClient): if isinstance(i, pyrogram_types.InputMediaPhoto): if os.path.exists(i.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedPhoto( - file=self.save_file(i.media) + file=await self.save_file(i.media) ) ) ) @@ -104,11 +104,11 @@ class SendMediaGroup(BaseClient): ) elif isinstance(i, pyrogram_types.InputMediaVideo): if os.path.exists(i.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( - file=self.save_file(i.media), + file=await self.save_file(i.media), mime_type=mimetypes.types_map[".mp4"], attributes=[ types.DocumentAttributeVideo( @@ -160,9 +160,9 @@ class SendMediaGroup(BaseClient): ) ) - return self.send( + return await self.send( functions.messages.SendMultiMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), multi_media=multi_media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id diff --git a/pyrogram/client/methods/messages/media/send_photo.py b/pyrogram/client/methods/messages/media/send_photo.py index 52e98ff1..e066deef 100644 --- a/pyrogram/client/methods/messages/media/send_photo.py +++ b/pyrogram/client/methods/messages/media/send_photo.py @@ -26,17 +26,17 @@ from ....ext import BaseClient, utils class SendPhoto(BaseClient): - def send_photo(self, - chat_id: int or str, - photo: str, - caption: str = "", - parse_mode: str = "", - ttl_seconds: int = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_photo(self, + chat_id: int or str, + photo: str, + caption: str = "", + parse_mode: str = "", + ttl_seconds: int = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send photos. Args: @@ -109,7 +109,7 @@ class SendPhoto(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(photo): - file = self.save_file(photo, progress=progress, progress_args=progress_args) + file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds @@ -145,9 +145,9 @@ class SendPhoto(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -157,11 +157,11 @@ class SendPhoto(BaseClient): ) ) except FilePartMissing as e: - self.save_file(photo, file_id=file.id, file_part=e.x) + await self.save_file(photo, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_sticker.py b/pyrogram/client/methods/messages/media/send_sticker.py index 639e3600..7d559d1c 100644 --- a/pyrogram/client/methods/messages/media/send_sticker.py +++ b/pyrogram/client/methods/messages/media/send_sticker.py @@ -26,14 +26,14 @@ from ....ext import BaseClient, utils class SendSticker(BaseClient): - def send_sticker(self, - chat_id: int or str, - sticker: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_sticker(self, + chat_id: int or str, + sticker: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send .webp stickers. Args: @@ -92,7 +92,7 @@ class SendSticker(BaseClient): file = None if os.path.exists(sticker): - file = self.save_file(sticker, progress=progress, progress_args=progress_args) + file = await self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type="image/webp", file=file, @@ -129,9 +129,9 @@ class SendSticker(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -141,11 +141,11 @@ class SendSticker(BaseClient): ) ) except FilePartMissing as e: - self.save_file(sticker, file_id=file.id, file_part=e.x) + await self.save_file(sticker, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_venue.py b/pyrogram/client/methods/messages/media/send_venue.py index d65ea43b..dcb3639d 100644 --- a/pyrogram/client/methods/messages/media/send_venue.py +++ b/pyrogram/client/methods/messages/media/send_venue.py @@ -21,16 +21,16 @@ from ....ext import BaseClient, utils class SendVenue(BaseClient): - def send_venue(self, - chat_id: int or str, - latitude: float, - longitude: float, - title: str, - address: str, - foursquare_id: str = "", - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + async def send_venue(self, + chat_id: int or str, + latitude: float, + longitude: float, + title: str, + address: str, + foursquare_id: str = "", + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): """Use this method to send information about a venue. Args: @@ -72,9 +72,9 @@ class SendVenue(BaseClient): Raises: :class:`Error ` """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaVenue( geo_point=types.InputGeoPoint( lat=latitude, @@ -96,7 +96,7 @@ class SendVenue(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_video.py b/pyrogram/client/methods/messages/media/send_video.py index a4cc0309..f7c7d66d 100644 --- a/pyrogram/client/methods/messages/media/send_video.py +++ b/pyrogram/client/methods/messages/media/send_video.py @@ -27,21 +27,21 @@ from ....ext import BaseClient, utils class SendVideo(BaseClient): - def send_video(self, - chat_id: int or str, - video: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - supports_streaming: bool = True, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_video(self, + chat_id: int or str, + video: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + supports_streaming: bool = True, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send video files. Args: @@ -127,7 +127,7 @@ class SendVideo(BaseClient): if os.path.exists(video): thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) + file = await self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], file=file, @@ -171,9 +171,9 @@ class SendVideo(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -183,11 +183,11 @@ class SendVideo(BaseClient): ) ) except FilePartMissing as e: - self.save_file(video, file_id=file.id, file_part=e.x) + await self.save_file(video, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_video_note.py b/pyrogram/client/methods/messages/media/send_video_note.py index d7b417d5..6eb2c252 100644 --- a/pyrogram/client/methods/messages/media/send_video_note.py +++ b/pyrogram/client/methods/messages/media/send_video_note.py @@ -27,16 +27,16 @@ from ....ext import BaseClient, utils class SendVideoNote(BaseClient): - def send_video_note(self, - chat_id: int or str, - video_note: str, - duration: int = 0, - length: int = 1, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_video_note(self, + chat_id: int or str, + video_note: str, + duration: int = 0, + length: int = 1, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send video messages. Args: @@ -101,7 +101,7 @@ class SendVideoNote(BaseClient): file = None if os.path.exists(video_note): - file = self.save_file(video_note, progress=progress, progress_args=progress_args) + file = await self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], file=file, @@ -139,9 +139,9 @@ class SendVideoNote(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -151,11 +151,11 @@ class SendVideoNote(BaseClient): ) ) except FilePartMissing as e: - self.save_file(video_note, file_id=file.id, file_part=e.x) + await self.save_file(video_note, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/media/send_voice.py b/pyrogram/client/methods/messages/media/send_voice.py index ae21de6d..114ee073 100644 --- a/pyrogram/client/methods/messages/media/send_voice.py +++ b/pyrogram/client/methods/messages/media/send_voice.py @@ -27,17 +27,17 @@ from ....ext import BaseClient, utils class SendVoice(BaseClient): - def send_voice(self, - chat_id: int or str, - voice: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + async def send_voice(self, + chat_id: int or str, + voice: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send audio files. Args: @@ -108,7 +108,7 @@ class SendVoice(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(voice): - file = self.save_file(voice, progress=progress, progress_args=progress_args) + file = await self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"), file=file, @@ -148,9 +148,9 @@ class SendVoice(BaseClient): while True: try: - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -160,11 +160,11 @@ class SendVoice(BaseClient): ) ) except FilePartMissing as e: - self.save_file(voice, file_id=file.id, file_part=e.x) + await self.save_file(voice, file_id=file.id, file_part=e.x) else: for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 44acaa2e..0009d499 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -22,14 +22,14 @@ from ...ext import utils, BaseClient class SendMessage(BaseClient): - def send_message(self, - chat_id: int or str, - text: str, - parse_mode: str = "", - disable_web_page_preview: bool = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + async def send_message(self, + chat_id: int or str, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): """Use this method to send text messages. Args: @@ -69,9 +69,9 @@ class SendMessage(BaseClient): """ style = self.html if parse_mode.lower() == "html" else self.markdown - r = self.send( + r = await self.send( functions.messages.SendMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), no_webpage=disable_web_page_preview or None, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -91,7 +91,7 @@ class SendMessage(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/update/delete_messages.py b/pyrogram/client/methods/messages/update/delete_messages.py index 3d29bf55..2853ce25 100644 --- a/pyrogram/client/methods/messages/update/delete_messages.py +++ b/pyrogram/client/methods/messages/update/delete_messages.py @@ -21,10 +21,10 @@ from ....ext import BaseClient class DeleteMessages(BaseClient): - def delete_messages(self, - chat_id: int or str, - message_ids, - revoke: bool = True): + async def delete_messages(self, + chat_id: int or str, + message_ids, + revoke: bool = True): """Use this method to delete messages, including service messages, with the following limitations: - A message can only be deleted if it was sent less than 48 hours ago. @@ -56,18 +56,18 @@ class DeleteMessages(BaseClient): Raises: :class:`Error ` """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] if isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.DeleteMessages( channel=peer, id=message_ids ) ) else: - self.send( + await self.send( functions.messages.DeleteMessages( id=message_ids, revoke=revoke or None diff --git a/pyrogram/client/methods/messages/update/edit_message_caption.py b/pyrogram/client/methods/messages/update/edit_message_caption.py index 90bf26f7..e2bade97 100644 --- a/pyrogram/client/methods/messages/update/edit_message_caption.py +++ b/pyrogram/client/methods/messages/update/edit_message_caption.py @@ -21,12 +21,12 @@ from ....ext import BaseClient, utils class EditMessageCaption(BaseClient): - def edit_message_caption(self, - chat_id: int or str, - message_id: int, - caption: str, - parse_mode: str = "", - reply_markup=None): + async def edit_message_caption(self, + chat_id: int or str, + message_id: int, + caption: str, + parse_mode: str = "", + reply_markup=None): """Use this method to edit captions of messages. Args: @@ -58,9 +58,9 @@ class EditMessageCaption(BaseClient): """ style = self.html if parse_mode.lower() == "html" else self.markdown - r = self.send( + r = await self.send( functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, reply_markup=reply_markup.write() if reply_markup else None, **style.parse(caption) @@ -69,7 +69,7 @@ class EditMessageCaption(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/update/edit_message_reply_markup.py b/pyrogram/client/methods/messages/update/edit_message_reply_markup.py index 295eb258..ec7c7638 100644 --- a/pyrogram/client/methods/messages/update/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/update/edit_message_reply_markup.py @@ -21,10 +21,10 @@ from ....ext import BaseClient, utils class EditMessageReplyMarkup(BaseClient): - def edit_message_reply_markup(self, - chat_id: int or str, - message_id: int, - reply_markup=None): + async def edit_message_reply_markup(self, + chat_id: int or str, + message_id: int, + reply_markup=None): """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots). Args: @@ -48,9 +48,9 @@ class EditMessageReplyMarkup(BaseClient): :class:`Error ` """ - r = self.send( + r = await self.send( functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, reply_markup=reply_markup.write() if reply_markup else None ) @@ -58,7 +58,7 @@ class EditMessageReplyMarkup(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/update/edit_message_text.py b/pyrogram/client/methods/messages/update/edit_message_text.py index be7b380c..6fa50e71 100644 --- a/pyrogram/client/methods/messages/update/edit_message_text.py +++ b/pyrogram/client/methods/messages/update/edit_message_text.py @@ -21,13 +21,13 @@ from ....ext import BaseClient, utils class EditMessageText(BaseClient): - def edit_message_text(self, - chat_id: int or str, - message_id: int, - text: str, - parse_mode: str = "", - disable_web_page_preview: bool = None, - reply_markup=None): + async def edit_message_text(self, + chat_id: int or str, + message_id: int, + text: str, + parse_mode: str = "", + disable_web_page_preview: bool = None, + reply_markup=None): """Use this method to edit text messages. Args: @@ -62,9 +62,9 @@ class EditMessageText(BaseClient): """ style = self.html if parse_mode.lower() == "html" else self.markdown - r = self.send( + r = await self.send( functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, @@ -74,7 +74,7 @@ class EditMessageText(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} From e0fe9d3525ab6edeb3d4d2108f06a52a6d3caffb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 19 Jun 2018 13:48:49 +0200 Subject: [PATCH 039/155] Fix some methods not being async --- .../client/methods/password/change_cloud_password.py | 6 +++--- .../client/methods/password/enable_cloud_password.py | 6 +++--- .../client/methods/password/remove_cloud_password.py | 6 +++--- pyrogram/client/methods/users/get_me.py | 4 ++-- .../client/methods/users/get_user_profile_photos.py | 12 ++++++------ pyrogram/client/methods/users/get_users.py | 8 +++++--- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 045a0cc9..e066d8ba 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class ChangeCloudPassword(BaseClient): - def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""): + async def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""): """Use this method to change your Two-Step Verification password (Cloud Password) with a new one. Args: @@ -43,7 +43,7 @@ class ChangeCloudPassword(BaseClient): Raises: :class:`Error ` """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if isinstance(r, types.account.Password): current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest() @@ -51,7 +51,7 @@ class ChangeCloudPassword(BaseClient): new_salt = r.new_salt + os.urandom(8) new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest() - return self.send( + return await self.send( functions.account.UpdatePasswordSettings( current_password_hash=current_password_hash, new_settings=types.account.PasswordInputSettings( diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 639879cb..496430ad 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class EnableCloudPassword(BaseClient): - def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): + async def enable_cloud_password(self, password: str, hint: str = "", email: str = ""): """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account. This password will be asked when you log in on a new device in addition to the SMS code. @@ -45,13 +45,13 @@ class EnableCloudPassword(BaseClient): Raises: :class:`Error ` """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if isinstance(r, types.account.NoPassword): salt = r.new_salt + os.urandom(8) password_hash = sha256(salt + password.encode() + salt).digest() - return self.send( + return await self.send( functions.account.UpdatePasswordSettings( current_password_hash=salt, new_settings=types.account.PasswordInputSettings( diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index bfbb2c8b..5392433f 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class RemoveCloudPassword(BaseClient): - def remove_cloud_password(self, password: str): + async def remove_cloud_password(self, password: str): """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account. Args: @@ -36,12 +36,12 @@ class RemoveCloudPassword(BaseClient): Raises: :class:`Error ` """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if isinstance(r, types.account.Password): password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest() - return self.send( + return await self.send( functions.account.UpdatePasswordSettings( current_password_hash=password_hash, new_settings=types.account.PasswordInputSettings( diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 80ee65e9..f191e298 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -21,7 +21,7 @@ from ...ext import BaseClient, utils class GetMe(BaseClient): - def get_me(self): + async def get_me(self): """A simple method for testing your authorization. Requires no parameters. Returns: @@ -31,7 +31,7 @@ class GetMe(BaseClient): :class:`Error ` """ return utils.parse_user( - self.send( + await self.send( functions.users.GetFullUser( types.InputPeerSelf() ) diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py index 42fb84bb..a58e9d52 100644 --- a/pyrogram/client/methods/users/get_user_profile_photos.py +++ b/pyrogram/client/methods/users/get_user_profile_photos.py @@ -21,10 +21,10 @@ from ...ext import BaseClient, utils class GetUserProfilePhotos(BaseClient): - def get_user_profile_photos(self, - user_id: int or str, - offset: int = 0, - limit: int = 100): + async def get_user_profile_photos(self, + user_id: int or str, + offset: int = 0, + limit: int = 100): """Use this method to get a list of profile pictures for a user. Args: @@ -49,9 +49,9 @@ class GetUserProfilePhotos(BaseClient): :class:`Error ` """ return utils.parse_photos( - self.send( + await self.send( functions.photos.GetUserPhotos( - user_id=self.resolve_peer(user_id), + user_id=await self.resolve_peer(user_id), offset=offset, max_id=0, limit=limit diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 400e35a1..33c38900 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -16,12 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio + from pyrogram.api import functions from ...ext import BaseClient, utils class GetUsers(BaseClient): - def get_users(self, user_ids): + async def get_users(self, user_ids): """Use this method to get information about a user. You can retrieve up to 200 users at once. @@ -41,9 +43,9 @@ class GetUsers(BaseClient): """ is_iterable = not isinstance(user_ids, (int, str)) user_ids = list(user_ids) if is_iterable else [user_ids] - user_ids = [self.resolve_peer(i) for i in user_ids] + user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids]) - r = self.send( + r = await self.send( functions.users.GetUsers( id=user_ids ) From 399a7b6403329199c3726229b338e51ef3e359f6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 19 Jun 2018 14:02:49 +0200 Subject: [PATCH 040/155] Make Message bound methods async --- pyrogram/client/types/message.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/types/message.py b/pyrogram/client/types/message.py index 74f2a0a6..9b712c1f 100644 --- a/pyrogram/client/types/message.py +++ b/pyrogram/client/types/message.py @@ -310,14 +310,14 @@ class Message(Object): self.command = command self.reply_markup = reply_markup - def reply_text(self, - text: str, - quote: bool = None, - parse_mode: str = "", - disable_web_page_preview: bool = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None): + async def reply_text(self, + text: str, + quote: bool = None, + parse_mode: str = "", + disable_web_page_preview: bool = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None): """Use this method as a shortcut for: .. code-block:: python @@ -373,7 +373,7 @@ class Message(Object): if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_message( + return await self._client.send_message( chat_id=self.chat.id, text=text, parse_mode=parse_mode, @@ -383,9 +383,9 @@ class Message(Object): reply_markup=reply_markup ) - def forward(self, - chat_id: int or str, - disable_notification: bool = None): + async def forward(self, + chat_id: int or str, + disable_notification: bool = None): """Use this method as a shortcut for: .. code-block:: python @@ -418,14 +418,14 @@ class Message(Object): Raises: :class:`Error ` """ - return self._client.forward_messages( + return await self._client.forward_messages( chat_id=chat_id, from_chat_id=self.chat.id, message_ids=self.message_id, disable_notification=disable_notification ) - def delete(self, revoke: bool = True): + async def delete(self, revoke: bool = True): """Use this method as a shortcut for: .. code-block:: python @@ -453,7 +453,7 @@ class Message(Object): Raises: :class:`Error ` """ - self._client.delete_messages( + await self._client.delete_messages( chat_id=self.chat.id, message_ids=self.message_id, revoke=revoke From 6fcf41d8572e5d64d31ae97f3b40dccc232ab5bd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 20 Jun 2018 11:41:22 +0200 Subject: [PATCH 041/155] Client becomes async --- pyrogram/client/client.py | 276 +++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 150 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8eba760a..a3197d0c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import base64 import binascii import getpass @@ -28,7 +29,6 @@ import re import shutil import struct import tempfile -import threading import time from configparser import ConfigParser from datetime import datetime @@ -43,11 +43,11 @@ from pyrogram.api.errors import ( PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, VolumeLocNotFound, UserMigrate, FileIdInvalid) -from pyrogram.client.handlers import DisconnectHandler from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher -from .ext import utils, Syncer, BaseClient +from .ext import BaseClient, Syncer, utils +from .handlers import DisconnectHandler from .methods import Methods # Custom format for nice looking log lines @@ -114,7 +114,7 @@ class Client(Methods, BaseClient): be an empty string: "". Only applicable for new sessions. workers (``int``, *optional*): - Thread pool size for handling incoming updates. Defaults to 4. + Number of maximum concurrent workers for handling incoming updates. Defaults to 4. workdir (``str``, *optional*): Define a custom working directory. The working directory is the location in your filesystem @@ -168,15 +168,10 @@ class Client(Methods, BaseClient): self._proxy["enabled"] = True self._proxy.update(value) - async def start(self, debug: bool = False): + async def start(self): """Use this method to start the Client after creating it. Requires no parameters. - Args: - debug (``bool``, *optional*): - Enable or disable debug mode. When enabled, extra logging - lines will be printed out on your console. - Raises: :class:`Error ` """ @@ -188,7 +183,7 @@ class Client(Methods, BaseClient): self.session_name = self.session_name.split(":")[0] self.load_config() - self.load_session() + await self.load_session() self.session = Session( self.dc_id, @@ -204,9 +199,9 @@ class Client(Methods, BaseClient): if self.user_id is None: if self.token is None: - self.authorize_user() + await self.authorize_user() else: - self.authorize_bot() + await self.authorize_bot() self.save_session() @@ -217,38 +212,27 @@ class Client(Methods, BaseClient): self.peers_by_username = {} self.peers_by_phone = {} - self.get_dialogs() - self.get_contacts() + await self.get_dialogs() + await self.get_contacts() else: - self.send(functions.messages.GetPinnedDialogs()) - self.get_dialogs_chunk(0) + await self.send(functions.messages.GetPinnedDialogs()) + await self.get_dialogs_chunk(0) else: await self.send(functions.updates.GetState()) - # for i in range(self.UPDATES_WORKERS): - # self.updates_workers_list.append( - # Thread( - # target=self.updates_worker, - # name="UpdatesWorker#{}".format(i + 1) - # ) - # ) - # - # self.updates_workers_list[-1].start() - # - # for i in range(self.DOWNLOAD_WORKERS): - # self.download_workers_list.append( - # Thread( - # target=self.download_worker, - # name="DownloadWorker#{}".format(i + 1) - # ) - # ) - # - # self.download_workers_list[-1].start() - # - # self.dispatcher.start() + self.updates_worker_task = asyncio.ensure_future(self.updates_worker()) + + for _ in range(Client.DOWNLOAD_WORKERS): + self.download_worker_tasks.append( + asyncio.ensure_future(self.download_worker()) + ) + + log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) + + await self.dispatcher.start() + await Syncer.add(self) mimetypes.init() - # Syncer.add(self) async def stop(self): """Use this method to manually stop the Client. @@ -257,29 +241,26 @@ class Client(Methods, BaseClient): if not self.is_started: raise ConnectionError("Client is already stopped") - # Syncer.remove(self) - # self.dispatcher.stop() - # - # for _ in range(self.DOWNLOAD_WORKERS): - # self.download_queue.put(None) - # - # for i in self.download_workers_list: - # i.join() - # - # self.download_workers_list.clear() - # - # for _ in range(self.UPDATES_WORKERS): - # self.updates_queue.put(None) - # - # for i in self.updates_workers_list: - # i.join() - # - # self.updates_workers_list.clear() - # - # for i in self.media_sessions.values(): - # i.stop() - # - # self.media_sessions.clear() + await Syncer.remove(self) + await self.dispatcher.stop() + + for _ in range(Client.DOWNLOAD_WORKERS): + self.download_queue.put_nowait(None) + + for task in self.download_worker_tasks: + await task + + self.download_worker_tasks.clear() + + log.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) + + self.updates_queue.put_nowait(None) + await self.updates_worker_task + + for media_session in self.media_sessions.values(): + await media_session.stop() + + self.media_sessions.clear() self.is_started = False await self.session.stop() @@ -327,9 +308,9 @@ class Client(Methods, BaseClient): else: self.dispatcher.remove_handler(handler, group) - def authorize_bot(self): + async def authorize_bot(self): try: - r = self.send( + r = await self.send( functions.auth.ImportBotAuthorization( flags=0, api_id=self.api_id, @@ -338,10 +319,10 @@ class Client(Methods, BaseClient): ) ) except UserMigrate as e: - self.session.stop() + await self.session.stop() self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = await Auth(self.dc_id, self.test_mode, self._proxy).create() self.session = Session( self.dc_id, @@ -352,12 +333,12 @@ class Client(Methods, BaseClient): client=self ) - self.session.start() - self.authorize_bot() + await self.session.start() + await self.authorize_bot() else: self.user_id = r.user.id - def authorize_user(self): + async def authorize_user(self): phone_number_invalid_raises = self.phone_number is not None phone_code_invalid_raises = self.phone_code is not None password_hash_invalid_raises = self.password is not None @@ -378,7 +359,7 @@ class Client(Methods, BaseClient): self.phone_number = self.phone_number.strip("+") try: - r = self.send( + r = await self.send( functions.auth.SendCode( self.phone_number, self.api_id, @@ -386,10 +367,10 @@ class Client(Methods, BaseClient): ) ) except (PhoneMigrate, NetworkMigrate) as e: - self.session.stop() + await self.session.stop() self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = await Auth(self.dc_id, self.test_mode, self._proxy).create() self.session = Session( self.dc_id, @@ -399,9 +380,9 @@ class Client(Methods, BaseClient): self.api_id, client=self ) - self.session.start() + await self.session.start() - r = self.send( + r = await self.send( functions.auth.SendCode( self.phone_number, self.api_id, @@ -430,7 +411,7 @@ class Client(Methods, BaseClient): phone_code_hash = r.phone_code_hash if self.force_sms: - self.send( + await self.send( functions.auth.ResendCode( phone_number=self.phone_number, phone_code_hash=phone_code_hash @@ -446,7 +427,7 @@ class Client(Methods, BaseClient): try: if phone_registered: - r = self.send( + r = await self.send( functions.auth.SignIn( self.phone_number, phone_code_hash, @@ -455,7 +436,7 @@ class Client(Methods, BaseClient): ) else: try: - self.send( + await self.send( functions.auth.SignIn( self.phone_number, phone_code_hash, @@ -468,7 +449,7 @@ class Client(Methods, BaseClient): self.first_name = self.first_name if self.first_name is not None else input("First name: ") self.last_name = self.last_name if self.last_name is not None else input("Last name: ") - r = self.send( + r = await self.send( functions.auth.SignUp( self.phone_number, phone_code_hash, @@ -491,7 +472,7 @@ class Client(Methods, BaseClient): self.first_name = None except SessionPasswordNeeded as e: print(e.MESSAGE) - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) while True: try: @@ -505,7 +486,7 @@ class Client(Methods, BaseClient): password_hash = sha256(self.password).digest() - r = self.send(functions.auth.CheckPassword(password_hash)) + r = await self.send(functions.auth.CheckPassword(password_hash)) except PasswordHashInvalid as e: if password_hash_invalid_raises: raise @@ -594,12 +575,9 @@ class Client(Methods, BaseClient): if username is not None: self.peers_by_username[username.lower()] = input_peer - def download_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) - + async def download_worker(self): while True: - media = self.download_queue.get() + media = await self.download_queue.get() if media is None: break @@ -666,7 +644,7 @@ class Client(Methods, BaseClient): extension ) - temp_file_path = self.get_file( + temp_file_path = await self.get_file( dc_id=dc_id, id=id, access_hash=access_hash, @@ -697,14 +675,11 @@ class Client(Methods, BaseClient): finally: done.set() - log.debug("{} stopped".format(name)) - - def updates_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) + async def updates_worker(self): + log.info("UpdatesWorkerTask started") while True: - updates = self.updates_queue.get() + updates = await self.updates_queue.get() if updates is None: break @@ -730,9 +705,9 @@ class Client(Methods, BaseClient): message = update.message if not isinstance(message, types.MessageEmpty): - diff = self.send( + diff = await self.send( functions.updates.GetChannelDifference( - channel=self.resolve_peer(int("-100" + str(channel_id))), + channel=await self.resolve_peer(int("-100" + str(channel_id))), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, @@ -760,9 +735,9 @@ class Client(Methods, BaseClient): if len(self.channels_pts[channel_id]) > 50: self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] - self.dispatcher.updates.put((update, updates.users, updates.chats)) + self.dispatcher.updates.put_nowait((update, updates.users, updates.chats)) elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): - diff = self.send( + diff = await self.send( functions.updates.GetDifference( pts=updates.pts - updates.pts_count, date=updates.date, @@ -771,7 +746,7 @@ class Client(Methods, BaseClient): ) if diff.new_messages: - self.dispatcher.updates.put(( + self.dispatcher.updates.put_nowait(( types.UpdateNewMessage( message=diff.new_messages[0], pts=updates.pts, @@ -781,18 +756,19 @@ class Client(Methods, BaseClient): diff.chats )) else: - self.dispatcher.updates.put((diff.other_updates[0], [], [])) + self.dispatcher.updates.put_nowait((diff.other_updates[0], [], [])) elif isinstance(updates, types.UpdateShort): - self.dispatcher.updates.put((updates.update, [], [])) + self.dispatcher.updates.put_nowait((updates.update, [], [])) except Exception as e: log.error(e, exc_info=True) - log.debug("{} stopped".format(name)) + log.info("UpdatesWorkerTask stopped") def signal_handler(self, *args): + log.info("Stop signal received ({}). Exiting...".format(args[0])) self.is_idle = False - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + async def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. @@ -807,9 +783,9 @@ class Client(Methods, BaseClient): self.is_idle = True while self.is_idle: - time.sleep(1) + await asyncio.sleep(1) - self.stop() + await self.stop() async def send(self, data: Object): """Use this method to send Raw Function queries. @@ -863,14 +839,14 @@ class Client(Methods, BaseClient): self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None - def load_session(self): + async def load_session(self): try: with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: s = json.load(f) except FileNotFoundError: self.dc_id = 1 self.date = 0 - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = await Auth(self.dc_id, self.test_mode, self._proxy).create() else: self.dc_id = s["dc_id"] self.test_mode = s["test_mode"] @@ -912,10 +888,10 @@ class Client(Methods, BaseClient): indent=4 ) - def get_dialogs_chunk(self, offset_date): + async def get_dialogs_chunk(self, offset_date): while True: try: - r = self.send( + r = await self.send( functions.messages.GetDialogs( offset_date, 0, types.InputPeerEmpty(), self.DIALOGS_AT_ONCE, True @@ -923,24 +899,24 @@ class Client(Methods, BaseClient): ) except FloodWait as e: log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) else: log.info("Total peers: {}".format(len(self.peers_by_id))) return r - def get_dialogs(self): - self.send(functions.messages.GetPinnedDialogs()) + async def get_dialogs(self): + await self.send(functions.messages.GetPinnedDialogs()) - dialogs = self.get_dialogs_chunk(0) + dialogs = await self.get_dialogs_chunk(0) offset_date = utils.get_offset_date(dialogs) while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: - dialogs = self.get_dialogs_chunk(offset_date) + dialogs = await self.get_dialogs_chunk(offset_date) offset_date = utils.get_offset_date(dialogs) - self.get_dialogs_chunk(0) + await self.get_dialogs_chunk(0) - def resolve_peer(self, peer_id: int or str): + async def resolve_peer(self, peer_id: int or str): """Use this method to get the *InputPeer* of a known *peer_id*. It is intended to be used when working with Raw Functions (i.e: a Telegram API method you wish to use which is @@ -968,7 +944,7 @@ class Client(Methods, BaseClient): try: decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_") - return self.resolve_peer(struct.unpack(">2iq", decoded)[1]) + return await self.resolve_peer(struct.unpack(">2iq", decoded)[1]) except (AttributeError, binascii.Error, struct.error): pass @@ -980,7 +956,7 @@ class Client(Methods, BaseClient): try: return self.peers_by_username[peer_id] except KeyError: - self.send(functions.contacts.ResolveUsername(peer_id)) + await self.send(functions.contacts.ResolveUsername(peer_id)) return self.peers_by_username[peer_id] else: try: @@ -1007,12 +983,12 @@ class Client(Methods, BaseClient): except (KeyError, ValueError): raise PeerIdInvalid - def save_file(self, - path: str, - file_id: int = None, - file_part: int = 0, - progress: callable = None, - progress_args: tuple = ()): + async def save_file(self, + path: str, + file_id: int = None, + file_part: int = 0, + progress: callable = None, + progress_args: tuple = ()): part_size = 512 * 1024 file_size = os.path.getsize(path) file_total_parts = int(math.ceil(file_size / part_size)) @@ -1022,7 +998,7 @@ class Client(Methods, BaseClient): md5_sum = md5() if not is_big and not is_missing_part else None session = Session(self.dc_id, self.test_mode, self._proxy, self.auth_key, self.api_id) - session.start() + await session.start() try: with open(path, "rb") as f: @@ -1050,7 +1026,7 @@ class Client(Methods, BaseClient): bytes=chunk ) - assert self.send(rpc), "Couldn't upload file" + assert await session.send(rpc), "Couldn't upload file" if is_missing_part: return @@ -1080,25 +1056,25 @@ class Client(Methods, BaseClient): md5_checksum=md5_sum ) finally: - session.stop() + await session.stop() - def get_file(self, - dc_id: int, - id: int = None, - access_hash: int = None, - volume_id: int = None, - local_id: int = None, - secret: int = None, - version: int = 0, - size: int = None, - progress: callable = None, - progress_args: tuple = None) -> str: - with self.media_sessions_lock: + async def get_file(self, + dc_id: int, + id: int = None, + access_hash: int = None, + volume_id: int = None, + local_id: int = None, + secret: int = None, + version: int = 0, + size: int = None, + progress: callable = None, + progress_args: tuple = None) -> str: + with await self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: if dc_id != self.dc_id: - exported_auth = self.send( + exported_auth = await self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) @@ -1108,15 +1084,15 @@ class Client(Methods, BaseClient): dc_id, self.test_mode, self._proxy, - Auth(dc_id, self.test_mode, self._proxy).create(), + await Auth(dc_id, self.test_mode, self._proxy).create(), self.api_id ) - session.start() + await session.start() self.media_sessions[dc_id] = session - session.send( + await session.send( functions.auth.ImportAuthorization( id=exported_auth.id, bytes=exported_auth.bytes @@ -1131,7 +1107,7 @@ class Client(Methods, BaseClient): self.api_id ) - session.start() + await session.start() self.media_sessions[dc_id] = session @@ -1153,7 +1129,7 @@ class Client(Methods, BaseClient): file_name = "" try: - r = session.send( + r = await session.send( functions.upload.GetFile( location=location, offset=offset, @@ -1180,7 +1156,7 @@ class Client(Methods, BaseClient): if progress: progress(self, min(offset, size), size, *progress_args) - r = session.send( + r = await session.send( functions.upload.GetFile( location=location, offset=offset, @@ -1189,7 +1165,7 @@ class Client(Methods, BaseClient): ) elif isinstance(r, types.upload.FileCdnRedirect): - with self.media_sessions_lock: + with await self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: @@ -1197,12 +1173,12 @@ class Client(Methods, BaseClient): r.dc_id, self.test_mode, self._proxy, - Auth(r.dc_id, self.test_mode, self._proxy).create(), + await Auth(r.dc_id, self.test_mode, self._proxy).create(), self.api_id, is_cdn=True ) - cdn_session.start() + await cdn_session.start() self.media_sessions[r.dc_id] = cdn_session @@ -1211,7 +1187,7 @@ class Client(Methods, BaseClient): file_name = f.name while True: - r2 = cdn_session.send( + r2 = await cdn_session.send( functions.upload.GetCdnFile( file_token=r.file_token, offset=offset, @@ -1221,7 +1197,7 @@ class Client(Methods, BaseClient): if isinstance(r2, types.upload.CdnFileReuploadNeeded): try: - session.send( + await session.send( functions.upload.ReuploadCdnFile( file_token=r.file_token, request_token=r2.request_token @@ -1244,7 +1220,7 @@ class Client(Methods, BaseClient): ) ) - hashes = session.send( + hashes = await session.send( functions.upload.GetCdnFileHashes( r.file_token, offset From 532ad6bd81346c9eb46bacb3eccc9627465dfe6c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 21 Jun 2018 18:02:16 +0200 Subject: [PATCH 042/155] Fix develop merge issues with asyncio branch --- pyrogram/client/dispatcher/dispatcher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 69425c08..2b597a72 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -182,13 +182,12 @@ class Dispatcher: (update.channel_id if is_channel else None) ) - self.dispatch( + await self.dispatch( pyrogram.Update( deleted_messages=(messages if not is_channel else None), deleted_channel_posts=(messages if is_channel else None) ) ) - elif isinstance(update, types.UpdateBotCallbackQuery): await self.dispatch( pyrogram.Update( From f5659841c2d8895d1a1117c255d9617254f4bef8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 21 Jun 2018 20:01:05 +0200 Subject: [PATCH 043/155] Reformat files --- pyrogram/client/handlers/__init__.py | 2 +- .../client/methods/decorators/__init__.py | 2 +- .../client/methods/messages/media/send_gif.py | 26 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index d06b2a76..0b5058e9 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler from .message_handler import MessageHandler -from .deleted_messages_handler import DeletedMessagesHandler from .raw_update_handler import RawUpdateHandler diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index f84a922c..d45a9ee6 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -17,9 +17,9 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect from .on_message import OnMessage -from .on_deleted_messages import OnDeletedMessages from .on_raw_update import OnRawUpdate diff --git a/pyrogram/client/methods/messages/media/send_gif.py b/pyrogram/client/methods/messages/media/send_gif.py index bdda234e..5c19a19b 100644 --- a/pyrogram/client/methods/messages/media/send_gif.py +++ b/pyrogram/client/methods/messages/media/send_gif.py @@ -28,19 +28,19 @@ from ....ext import BaseClient, utils class SendGIF(BaseClient): async def send_gif(self, - chat_id: int or str, - gif: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): + chat_id: int or str, + gif: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): """Use this method to send GIF files. Args: From 5446801c14c8789657c8b9b7fba276a350f34a54 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 22 Jun 2018 13:39:29 +0200 Subject: [PATCH 044/155] Make run() run the event loop --- pyrogram/client/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index ddfc3dc2..26b688de 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -294,8 +294,12 @@ class Client(Methods, BaseClient): Raises: :class:`Error ` """ - self.start() - self.idle() + asyncio.get_event_loop().run_until_complete( + asyncio.gather( + self.start(), + self.idle() + ) + ) def add_handler(self, handler, group: int = 0): """Use this method to register an update handler. From 7ba29065327de11c117af83dc135cd61aaa1c7b5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jun 2018 14:31:21 +0200 Subject: [PATCH 045/155] Make request_callback_answer async --- .../client/methods/bots/request_callback_answer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 5bc31efd..4ca72592 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -21,10 +21,10 @@ from pyrogram.client.ext import BaseClient class RequestCallbackAnswer(BaseClient): - def request_callback_answer(self, - chat_id: int or str, - message_id: int, - callback_data: str): + async def request_callback_answer(self, + chat_id: int or str, + message_id: int, + callback_data: str): """Use this method to request a callback answer from bots. This is the equivalent of clicking an inline button containing callback data. The answer contains info useful for clients to display a notification at the top of the chat screen or as an alert. @@ -42,7 +42,7 @@ class RequestCallbackAnswer(BaseClient): callback_data (``str``): Callback data associated with the inline button you want to get the answer from. """ - return self.send( + return await self.send( functions.messages.GetBotCallbackAnswer( peer=self.resolve_peer(chat_id), msg_id=message_id, From c9cd79cb0566e9f21a9e813e374a96cc606516d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jun 2018 15:49:56 +0200 Subject: [PATCH 046/155] Fix merge mess with duplicated idle() methods --- pyrogram/client/client.py | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 23c2abcc..2025c524 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -265,7 +265,7 @@ class Client(Methods, BaseClient): self.is_started = False await self.session.stop() - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + async def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Blocks the program execution until one of the signals are received, then gently stop the Client by closing the underlying connection. @@ -275,6 +275,7 @@ class Client(Methods, BaseClient): Defaults to (SIGINT, SIGTERM, SIGABRT). """ def signal_handler(*args): + log.info("Stop signal received ({}). Exiting...".format(args[0])) self.is_idle = False for s in stop_signals: @@ -283,9 +284,9 @@ class Client(Methods, BaseClient): self.is_idle = True while self.is_idle: - time.sleep(1) + await asyncio.sleep(1) - self.stop() + await self.stop() def run(self): """Use this method to automatically start and idle a Client. @@ -800,29 +801,6 @@ class Client(Methods, BaseClient): log.info("UpdatesWorkerTask stopped") - def signal_handler(self, *args): - log.info("Stop signal received ({}). Exiting...".format(args[0])) - self.is_idle = False - - async def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): - """Blocks the program execution until one of the signals are received, - then gently stop the Client by closing the underlying connection. - - Args: - stop_signals (``tuple``, *optional*): - Iterable containing signals the signal handler will listen to. - Defaults to (SIGINT, SIGTERM, SIGABRT). - """ - for s in stop_signals: - signal(s, self.signal_handler) - - self.is_idle = True - - while self.is_idle: - await asyncio.sleep(1) - - await self.stop() - async def send(self, data: Object): """Use this method to send Raw Function queries. From d06097c68abb2a00a9c1b819ecf73c53cbd249eb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jun 2018 15:53:56 +0200 Subject: [PATCH 047/155] Use uvloop, if available --- pyrogram/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 531da722..f6bd5321 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import sys __copyright__ = "Copyright (C) 2017-2018 Dan Tès ".replace( @@ -41,3 +42,10 @@ from .client import ( MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, RawUpdateHandler, DisconnectHandler, Filters ) + +try: + import uvloop +except ImportError: + pass +else: + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) From 06cb2a1168da1a7b91dd960723492482c355d5b0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jun 2018 16:00:37 +0200 Subject: [PATCH 048/155] Move try..except block at the top --- pyrogram/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index f6bd5321..298b52c2 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -19,6 +19,13 @@ import asyncio import sys +try: + import uvloop +except ImportError: + pass +else: + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + __copyright__ = "Copyright (C) 2017-2018 Dan Tès ".replace( "\xe8", "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" @@ -42,10 +49,3 @@ from .client import ( MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, RawUpdateHandler, DisconnectHandler, Filters ) - -try: - import uvloop -except ImportError: - pass -else: - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) From 5834e38f14162c6ceb25f06106989912a41f7584 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Jun 2018 11:39:50 +0200 Subject: [PATCH 049/155] Make run() accept a coroutine --- pyrogram/client/client.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2025c524..e6080738 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -288,19 +288,25 @@ class Client(Methods, BaseClient): await self.stop() - def run(self): + def run(self, coroutine=None): """Use this method to automatically start and idle a Client. - Requires no parameters. + If a coroutine is passed as argument this method will start the client, run the coroutine + until is complete and then stop the client automatically. + + Args: + coroutine: (``Coroutine``, *optional*): + Pass a coroutine to run it until is complete. Raises: :class:`Error ` """ - asyncio.get_event_loop().run_until_complete( - asyncio.gather( - self.start(), - self.idle() - ) - ) + run = asyncio.get_event_loop().run_until_complete + + run(self.start()) + run(coroutine or self.idle()) + + if coroutine: + run(self.stop()) def add_handler(self, handler, group: int = 0): """Use this method to register an update handler. From 81c8fca11c2485257828538e13c51d745ef17bf3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Jun 2018 11:40:43 +0200 Subject: [PATCH 050/155] Make the on_disconnect callback function a coroutine --- pyrogram/session/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6479dfdd..a41ff5fa 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -197,7 +197,7 @@ class Session: if self.client and callable(self.client.disconnect_handler): try: - self.client.disconnect_handler(self.client) + await self.client.disconnect_handler(self.client) except Exception as e: log.error(e, exc_info=True) From 9dff15bd4f364c03fabdc8905e7dd755ecba2c7c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 26 Jun 2018 13:45:31 +0200 Subject: [PATCH 051/155] Make run() accept coroutine functions --- pyrogram/client/client.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8c34ea29..61b499a3 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -20,6 +20,7 @@ import asyncio import base64 import binascii import getpass +import inspect import json import logging import math @@ -328,11 +329,18 @@ class Client(Methods, BaseClient): run = asyncio.get_event_loop().run_until_complete run(self.start()) - run(coroutine or self.idle()) - if coroutine: + run( + coroutine if inspect.iscoroutine(coroutine) + else coroutine() if coroutine + else self.idle() + ) + + if self.is_started: run(self.stop()) + return coroutine + def add_handler(self, handler, group: int = 0): """Use this method to register an update handler. From 2f1d44778330218e36a65adeac0241fea86b8327 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 Jun 2018 17:50:37 +0200 Subject: [PATCH 052/155] Move INITIAL_SALT to Session --- pyrogram/crypto/mtproto.py | 2 -- pyrogram/session/session.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 10839126..539976d6 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -25,8 +25,6 @@ from . import AES, KDF class MTProto: - INITIAL_SALT = 0x616e67656c696361 - @staticmethod def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> bytes: data = Long(salt) + session_id + message.write() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ca12c92b..981e61cd 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -117,7 +117,7 @@ class Session: self.net_worker_task = asyncio.ensure_future(self.net_worker()) self.recv_task = asyncio.ensure_future(self.recv()) - self.current_salt = FutureSalt(0, 0, MTProto.INITIAL_SALT) + self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt(0, 0, (await self._send(functions.Ping(0))).new_server_salt) self.current_salt = (await self._send(functions.GetFutureSalts(1))).salts[0] From 335a2e06c82b83bb38ad5c4d18efbdd8d484e098 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 Jun 2018 20:14:38 +0200 Subject: [PATCH 053/155] Make delete_profile_photos async --- pyrogram/client/methods/users/delete_profile_photos.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index 47a6682a..cbbf3c20 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class DeleteProfilePhotos(BaseClient): - def delete_profile_photos(self, id: str or list): + async def delete_profile_photos(self, id: str or list): """Use this method to delete your own profile photos Args: @@ -51,7 +51,7 @@ class DeleteProfilePhotos(BaseClient): ) ) - return bool(self.send( + return bool(await self.send( functions.photos.DeletePhotos( id=input_photos ) From 984e989a4b032dc4af6f3da74dd83310bb78f033 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 30 Jun 2018 11:03:55 +0200 Subject: [PATCH 054/155] Lock TCP send() --- pyrogram/connection/transport/tcp/tcp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index f541153e..7ca479a5 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -39,6 +39,8 @@ class TCP: def __init__(self, proxy: dict): self.proxy = proxy + self.lock = asyncio.Lock() + self.socket = socks.socksocket() self.reader = None # type: asyncio.StreamReader self.writer = None # type: asyncio.StreamWriter @@ -76,8 +78,9 @@ class TCP: self.socket.close() async def send(self, data: bytes): - self.writer.write(data) - await self.writer.drain() + with await self.lock: + self.writer.write(data) + await self.writer.drain() async def recv(self, length: int = 0): data = b"" From aa800c3ebc671d492376f9428da7674f2d9adb3f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 30 Jun 2018 11:04:17 +0200 Subject: [PATCH 055/155] Reformat code --- pyrogram/connection/transport/tcp/tcp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 7ca479a5..062f1d2b 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -42,10 +42,11 @@ class TCP: self.lock = asyncio.Lock() self.socket = socks.socksocket() + self.socket.settimeout(TCP.TIMEOUT) + self.reader = None # type: asyncio.StreamReader self.writer = None # type: asyncio.StreamWriter - self.socket.settimeout(TCP.TIMEOUT) self.proxy_enabled = proxy.get("enabled", False) if proxy and self.proxy_enabled: From d28f795acaa05a88eb0c713dc5dad689af8ff0e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 30 Jun 2018 11:26:45 +0200 Subject: [PATCH 056/155] Make save_file more efficient --- pyrogram/client/client.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 39a91db3..c4fc7e3a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1070,6 +1070,15 @@ class Client(Methods, BaseClient): file_part: int = 0, progress: callable = None, progress_args: tuple = ()): + async def worker(): + while True: + data = await queue.get() + + if data is None: + return + + await asyncio.ensure_future(session.send(data)) + part_size = 512 * 1024 file_size = os.path.getsize(path) file_total_parts = int(math.ceil(file_size / part_size)) @@ -1077,11 +1086,13 @@ class Client(Methods, BaseClient): is_missing_part = True if file_id is not None else False file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - session = Session(self, self.dc_id, self.auth_key, is_media=True) - await session.start() + workers = [asyncio.ensure_future(worker()) for _ in range(4)] + queue = asyncio.Queue(16) try: + await session.start() + with open(path, "rb") as f: f.seek(part_size * file_part) @@ -1107,7 +1118,7 @@ class Client(Methods, BaseClient): bytes=chunk ) - assert await session.send(rpc), "Couldn't upload file" + await queue.put(rpc) if is_missing_part: return @@ -1137,6 +1148,10 @@ class Client(Methods, BaseClient): md5_checksum=md5_sum ) finally: + for _ in workers: + await queue.put(None) + + await asyncio.gather(*workers) await session.stop() async def get_file(self, From b49030eb10b8a2fc7c737683e53f29c115591f23 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 30 Jun 2018 11:30:32 +0200 Subject: [PATCH 057/155] Shorter conditions --- pyrogram/client/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index c4fc7e3a..cee39141 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1082,8 +1082,8 @@ class Client(Methods, BaseClient): part_size = 512 * 1024 file_size = os.path.getsize(path) file_total_parts = int(math.ceil(file_size / part_size)) - is_big = True if file_size > 10 * 1024 * 1024 else False - is_missing_part = True if file_id is not None else False + is_big = file_size > 10 * 1024 * 1024 + is_missing_part = file_id is not None file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None session = Session(self, self.dc_id, self.auth_key, is_media=True) From 26bb97af466748764e76e59198302037e7ec3236 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 14:10:26 +0200 Subject: [PATCH 058/155] Add ainput function --- pyrogram/client/ext/utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index be484823..3a275942 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -16,9 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging +import sys import time from base64 import b64decode, b64encode +from concurrent.futures.thread import ThreadPoolExecutor from struct import pack from weakref import proxy @@ -57,6 +60,13 @@ class Str(str): return self._client.html.unparse(self, self._entities) +async def ainput(prompt: str = ""): + with ThreadPoolExecutor(1, "AsyncInput", lambda x: print(x, end="", flush=True), (prompt,)) as executor: + return (await asyncio.get_event_loop().run_in_executor( + executor, sys.stdin.readline + )).rstrip() + + ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", From af5c5d20cff97cf1fe3594794880db1354a5f967 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 14:10:48 +0200 Subject: [PATCH 059/155] Replace input() with ainput() in Client --- pyrogram/client/client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 867cd3f6..fb14d379 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -48,6 +48,7 @@ from pyrogram.crypto import AES from pyrogram.session import Auth, Session from .dispatcher import Dispatcher from .ext import BaseClient, Syncer, utils +from .ext.utils import ainput from .handlers import DisconnectHandler from .methods import Methods @@ -413,15 +414,15 @@ class Client(Methods, BaseClient): while True: if self.phone_number is None: - self.phone_number = input("Enter phone number: ") + self.phone_number = await ainput("Enter phone number: ") while True: - confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number)) + confirm = await ainput("Is \"{}\" correct? (y/n): ".format(self.phone_number)) if confirm in ("y", "1"): break elif confirm in ("n", "2"): - self.phone_number = input("Enter phone number: ") + self.phone_number = await ainput("Enter phone number: ") self.phone_number = self.phone_number.strip("+") @@ -488,7 +489,7 @@ class Client(Methods, BaseClient): while True: self.phone_code = ( - input("Enter phone code: ") if self.phone_code is None + await ainput("Enter phone code: ") if self.phone_code is None else self.phone_code if type(self.phone_code) is str else str(self.phone_code(self.phone_number)) ) @@ -514,8 +515,8 @@ class Client(Methods, BaseClient): except PhoneNumberUnoccupied: pass - self.first_name = self.first_name if self.first_name is not None else input("First name: ") - self.last_name = self.last_name if self.last_name is not None else input("Last name: ") + self.first_name = self.first_name if self.first_name is not None else await ainput("First name: ") + self.last_name = self.last_name if self.last_name is not None else await ainput("Last name: ") r = await self.send( functions.auth.SignUp( From ed562edb9f93155a6bd5de3351c12a371871e2a9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 14:11:02 +0200 Subject: [PATCH 060/155] Fix send AcceptTermsOfService not being awaited --- pyrogram/client/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index fb14d379..e56a0233 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -585,7 +585,7 @@ class Client(Methods, BaseClient): break if terms_of_service: - assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) + assert await self.send(functions.help.AcceptTermsOfService(terms_of_service.id)) self.password = None self.user_id = r.user.id From ec82b4f994aa955e209efb69f19efc4c197e5e52 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 17:21:42 +0200 Subject: [PATCH 061/155] Don't use getpass anymore (for now) The reason is that getpass is blocking. Let's use ainput() until a proper way of reading from stdin without echoing is found. --- pyrogram/client/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index e56a0233..6ab9b17a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -19,7 +19,6 @@ import asyncio import base64 import binascii -import getpass import inspect import json import logging @@ -548,7 +547,7 @@ class Client(Methods, BaseClient): if self.password is None: print("Hint: {}".format(r.hint)) - self.password = getpass.getpass("Enter password: ") + self.password = await ainput("Enter password: ") if type(self.password) is str: self.password = r.current_salt + self.password.encode() + r.current_salt From f4c583664a5a28fa7cc81f35301943197745473a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 19:14:30 +0200 Subject: [PATCH 062/155] Remove unsupported arguments for Python <3.7 --- pyrogram/client/ext/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 3a275942..5161d370 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -61,7 +61,9 @@ class Str(str): async def ainput(prompt: str = ""): - with ThreadPoolExecutor(1, "AsyncInput", lambda x: print(x, end="", flush=True), (prompt,)) as executor: + print(prompt, end="", flush=True) + + with ThreadPoolExecutor(1, "AsyncInput") as executor: return (await asyncio.get_event_loop().run_in_executor( executor, sys.stdin.readline )).rstrip() From 219988740c2b5e0a4ff8fd265745062053efd30d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 19:16:01 +0200 Subject: [PATCH 063/155] Remove unsupported argument for Python <3.6 --- pyrogram/client/ext/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 5161d370..ae3a9763 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -63,7 +63,7 @@ class Str(str): async def ainput(prompt: str = ""): print(prompt, end="", flush=True) - with ThreadPoolExecutor(1, "AsyncInput") as executor: + with ThreadPoolExecutor(1) as executor: return (await asyncio.get_event_loop().run_in_executor( executor, sys.stdin.readline )).rstrip() From dc7c9af826498cdbefb7eba4d7e04de279ae6f45 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 2 Jul 2018 20:47:45 +0200 Subject: [PATCH 064/155] Set v0.8.0dev1 for the asyncio branch This way people can easily tell whether they are running the correct branch or not (pip is misbehaving lately and installations from git don't replace files). --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5122cab9..635ec375 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Tue, 3 Jul 2018 16:34:55 +0200 Subject: [PATCH 065/155] Further improve save_file --- pyrogram/client/client.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 6ab9b17a..8ea4b629 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1072,14 +1072,17 @@ class Client(Methods, BaseClient): file_part: int = 0, progress: callable = None, progress_args: tuple = ()): - async def worker(): + async def worker(session): while True: data = await queue.get() if data is None: return - await asyncio.ensure_future(session.send(data)) + try: + await asyncio.ensure_future(session.send(data)) + except Exception as e: + log.error(e) part_size = 512 * 1024 file_size = os.path.getsize(path) @@ -1088,12 +1091,13 @@ class Client(Methods, BaseClient): is_missing_part = file_id is not None file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - session = Session(self, self.dc_id, self.auth_key, is_media=True) - workers = [asyncio.ensure_future(worker()) for _ in range(4)] + pool = [Session(self, self.dc_id, self.auth_key, is_media=True) for _ in range(3)] + workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(4)] queue = asyncio.Queue(16) try: - await session.start() + for session in pool: + await session.start() with open(path, "rb") as f: f.seek(part_size * file_part) @@ -1154,7 +1158,9 @@ class Client(Methods, BaseClient): await queue.put(None) await asyncio.gather(*workers) - await session.stop() + + for session in pool: + await session.stop() async def get_file(self, dc_id: int, From f2d64b25737866bb4d52e1baf2b62cea5c0d639f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 5 Jul 2018 15:06:25 +0200 Subject: [PATCH 066/155] Make get_dialogs async --- pyrogram/client/methods/messages/get_dialogs.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/methods/messages/get_dialogs.py b/pyrogram/client/methods/messages/get_dialogs.py index f43ca6a5..d8f47059 100644 --- a/pyrogram/client/methods/messages/get_dialogs.py +++ b/pyrogram/client/methods/messages/get_dialogs.py @@ -24,12 +24,12 @@ from ...ext import BaseClient, utils class GetDialogs(BaseClient): # TODO docstrings - def get_dialogs(self, - limit: int = 100, - pinned_only: bool = False, - last_chunk=None): + async def get_dialogs(self, + limit: int = 100, + pinned_only: bool = False, + last_chunk=None): if pinned_only: - r = self.send(functions.messages.GetPinnedDialogs()) + r = await self.send(functions.messages.GetPinnedDialogs()) else: offset_date = 0 @@ -44,7 +44,7 @@ class GetDialogs(BaseClient): offset_date = message_date break - r = self.send( + r = await self.send( functions.messages.GetDialogs( offset_date=offset_date, offset_id=0, @@ -72,7 +72,7 @@ class GetDialogs(BaseClient): else: chat_id = int("-100" + str(to_id.channel_id)) - messages[chat_id] = utils.parse_messages(self, message, users, chats) + messages[chat_id] = await utils.parse_messages(self, message, users, chats) dialogs = [] From ccd651f1fcd7d42070d062112827423e29f307b4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 17 Jul 2018 08:28:28 +0200 Subject: [PATCH 067/155] Make the new methods async --- .../client/methods/chats/delete_chat_photo.py | 8 ++++---- .../client/methods/chats/get_chat_members.py | 18 +++++++++--------- .../client/methods/chats/pin_chat_message.py | 6 +++--- .../methods/chats/set_chat_description.py | 6 +++--- .../client/methods/chats/set_chat_photo.py | 8 ++++---- .../client/methods/chats/set_chat_title.py | 8 ++++---- .../client/methods/chats/unpin_chat_message.py | 6 +++--- pyrogram/client/types/message.py | 4 ++-- 8 files changed, 32 insertions(+), 32 deletions(-) diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 57d90b11..d8eff6a4 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class DeleteChatPhoto(BaseClient): - def delete_chat_photo(self, chat_id: int or str): + async def delete_chat_photo(self, chat_id: int or str): """Use this method to delete a chat photo. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -42,17 +42,17 @@ class DeleteChatPhoto(BaseClient): :class:`Error ` ``ValueError``: If a chat_id belongs to user. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - self.send( + await self.send( functions.messages.EditChatPhoto( chat_id=peer.chat_id, photo=types.InputChatPhotoEmpty() ) ) elif isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.EditPhoto( channel=peer, photo=types.InputChatPhotoEmpty() diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index a851ef58..295a32c5 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -30,17 +30,17 @@ class Filters: class GetChatMembers(BaseClient): - def get_chat_members(self, - chat_id: int or str, - offset: int = 0, - limit: int = 200, - query: str = "", - filter: str = Filters.ALL): - peer = self.resolve_peer(chat_id) + async def get_chat_members(self, + chat_id: int or str, + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = Filters.ALL): + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): return utils.parse_chat_members( - self.send( + await self.send( functions.messages.GetFullChat( peer.chat_id ) @@ -65,7 +65,7 @@ class GetChatMembers(BaseClient): raise ValueError("Invalid filter \"{}\"".format(filter)) return utils.parse_chat_members( - self.send( + await self.send( functions.channels.GetParticipants( channel=peer, filter=filter, diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index e9bc533e..9e6f49c9 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class PinChatMessage(BaseClient): - def pin_chat_message(self, chat_id: int or str, message_id: int, disable_notification: bool = None): + async def pin_chat_message(self, chat_id: int or str, message_id: int, disable_notification: bool = None): """Use this method to pin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. @@ -45,10 +45,10 @@ class PinChatMessage(BaseClient): :class:`Error ` ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.UpdatePinnedMessage( channel=peer, id=message_id, diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index c9597a62..8f647818 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class SetChatDescription(BaseClient): - def set_chat_description(self, chat_id: int or str, description: str): + async def set_chat_description(self, chat_id: int or str, description: str): """Use this method to change the description of a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -40,10 +40,10 @@ class SetChatDescription(BaseClient): :class:`Error ` ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.EditAbout( channel=peer, about=description diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index d98fefde..558671b7 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -25,7 +25,7 @@ from ...ext import BaseClient class SetChatPhoto(BaseClient): - def set_chat_photo(self, chat_id: int or str, photo: str): + async def set_chat_photo(self, chat_id: int or str, photo: str): """Use this method to set a new profile photo for the chat. Photos can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -49,7 +49,7 @@ class SetChatPhoto(BaseClient): :class:`Error ` ``ValueError``: If a chat_id belongs to user. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) @@ -64,14 +64,14 @@ class SetChatPhoto(BaseClient): ) if isinstance(peer, types.InputPeerChat): - self.send( + await self.send( functions.messages.EditChatPhoto( chat_id=peer.chat_id, photo=photo ) ) elif isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.EditPhoto( channel=peer, photo=photo diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index f6644a01..2769ccb9 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class SetChatTitle(BaseClient): - def set_chat_title(self, chat_id: int or str, title: str): + async def set_chat_title(self, chat_id: int or str, title: str): """Use this method to change the title of a chat. Titles can't be changed for private chats. You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -45,17 +45,17 @@ class SetChatTitle(BaseClient): :class:`Error ` ``ValueError``: If a chat_id belongs to user. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - self.send( + await self.send( functions.messages.EditChatTitle( chat_id=peer.chat_id, title=title ) ) elif isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.EditTitle( channel=peer, title=title diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index b1eeec79..9b6b4144 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class UnpinChatMessage(BaseClient): - def unpin_chat_message(self, chat_id: int or str): + async def unpin_chat_message(self, chat_id: int or str): """Use this method to unpin a message in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. @@ -38,10 +38,10 @@ class UnpinChatMessage(BaseClient): :class:`Error ` ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - self.send( + await self.send( functions.channels.UpdatePinnedMessage( channel=peer, id=0 diff --git a/pyrogram/client/types/message.py b/pyrogram/client/types/message.py index c9137328..51de2a6f 100644 --- a/pyrogram/client/types/message.py +++ b/pyrogram/client/types/message.py @@ -572,7 +572,7 @@ class Message(Object): else: raise ValueError("The message doesn't contain any keyboard") - def download(self, file_name: str = "", block: bool = True): + async def download(self, file_name: str = "", block: bool = True): """Use this method as a shortcut for: .. code-block:: python @@ -602,7 +602,7 @@ class Message(Object): :class:`Error ` ``ValueError``: If the message doesn't contain any downloadable media """ - return self._client.download_media( + return await self._client.download_media( message=self, file_name=file_name, block=block From c3cf924ddd8017d2aff8c729b4207922459cf5e1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Aug 2018 10:32:57 +0200 Subject: [PATCH 068/155] Fix small merge issues --- pyrogram/client/methods/chats/get_dialogs.py | 8 ++--- .../client/methods/messages/send_animation.py | 30 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 8d9ad00f..13d46787 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -22,10 +22,10 @@ from ...ext import BaseClient, utils class GetDialogs(BaseClient): - def get_dialogs(self, - offset_dialogs=None, - limit: int = 100, - pinned_only: bool = False): + async def get_dialogs(self, + offset_dialogs=None, + limit: int = 100, + pinned_only: bool = False): """Use this method to get the user's dialogs You can get up to 100 dialogs at once. diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index ba68c77b..bd9cd0fd 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -27,21 +27,21 @@ from pyrogram.client.ext import BaseClient, utils class SendAnimation(BaseClient): - asyncdef send_animation(self, - chat_id: int or str, - animation: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): - """Use this method to send animation files(animation or H.264/MPEG-4 AVC video without sound). + async def send_animation(self, + chat_id: int or str, + animation: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). Args: chat_id (``int`` | ``str``): From aaaba4b84798e5afc69cc9d956058601e8183bcf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 23 Aug 2018 20:43:46 +0200 Subject: [PATCH 069/155] Update async branch version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 02170ab9..ee637268 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -31,7 +31,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès Date: Thu, 23 Aug 2018 21:07:19 +0200 Subject: [PATCH 070/155] Add missing async/await keywords --- .../client/methods/chats/get_chat_member.py | 10 +++--- .../methods/messages/edit_message_media.py | 32 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 51e07f91..bd43808f 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -21,7 +21,7 @@ from ...ext import BaseClient, utils class GetChatMember(BaseClient): - def get_chat_member(self, + async def get_chat_member(self, chat_id: int or str, user_id: int or str): """Use this method to get information about one member of a chat. @@ -41,11 +41,11 @@ class GetChatMember(BaseClient): Raises: :class:`Error ` """ - chat_id = self.resolve_peer(chat_id) - user_id = self.resolve_peer(user_id) + chat_id = await self.resolve_peer(chat_id) + user_id = await self.resolve_peer(user_id) if isinstance(chat_id, types.InputPeerChat): - full_chat = self.send( + full_chat = await self.send( functions.messages.GetFullChat( chat_id=chat_id.chat_id ) @@ -57,7 +57,7 @@ class GetChatMember(BaseClient): else: raise errors.UserNotParticipant elif isinstance(chat_id, types.InputPeerChannel): - r = self.send( + r = await self.send( functions.channels.GetParticipant( channel=chat_id, user_id=user_id diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 086d8dc2..5993f1c9 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -31,7 +31,7 @@ from pyrogram.client.types import ( class EditMessageMedia(BaseClient): - def edit_message_media(self, + async def edit_message_media(self, chat_id: int or str, message_id: int, media, @@ -41,11 +41,11 @@ class EditMessageMedia(BaseClient): if isinstance(media, InputMediaPhoto): if os.path.exists(media.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedPhoto( - file=self.save_file(media.media) + file=await self.save_file(media.media) ) ) ) @@ -85,12 +85,12 @@ class EditMessageMedia(BaseClient): if isinstance(media, InputMediaVideo): if os.path.exists(media.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], - file=self.save_file(media.media), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( supports_streaming=media.supports_streaming or None, @@ -139,12 +139,12 @@ class EditMessageMedia(BaseClient): if isinstance(media, InputMediaAudio): if os.path.exists(media.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "audio/mpeg"), - file=self.save_file(media.media), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( duration=media.duration, @@ -192,12 +192,12 @@ class EditMessageMedia(BaseClient): if isinstance(media, InputMediaAnimation): if os.path.exists(media.media): - media = self.send( + media = await self.send( functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], - file=self.save_file(media.media), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( supports_streaming=True, @@ -245,9 +245,9 @@ class EditMessageMedia(BaseClient): ) ) - r = self.send( + r = await self.send( functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, reply_markup=reply_markup.write() if reply_markup else None, media=media, @@ -257,7 +257,7 @@ class EditMessageMedia(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return utils.parse_messages( + return await utils.parse_messages( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} From 38442bf3c1acf418dc2030c4996cd95aae0bd89d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 Sep 2018 00:41:01 +0200 Subject: [PATCH 071/155] Add missing await --- pyrogram/session/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 34e892e0..fb3b5594 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -147,7 +147,7 @@ class Session: log.info("System: {} ({})".format(self.client.system_version, self.client.lang_code.upper())) except AuthKeyDuplicated as e: - self.stop() + await self.stop() raise e except (OSError, TimeoutError, Error): await self.stop() From 45a32ddd8894c3c7efad6070024c52ff8cce6566 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 Sep 2018 00:42:45 +0200 Subject: [PATCH 072/155] Remove old commented code on session.py --- pyrogram/session/session.py | 399 ------------------------------------ 1 file changed, 399 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index fb3b5594..fcd8f8e1 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -401,402 +401,3 @@ class Session: await asyncio.sleep(0.5) return await self.send(data, retries - 1, timeout) - -# class Result: -# def __init__(self): -# self.value = None -# self.event = Event() -# -# -# class Session: -# VERSION = __version__ -# APP_VERSION = "Pyrogram \U0001f525 {}".format(VERSION) -# -# DEVICE_MODEL = "{} {}".format( -# platform.python_implementation(), -# platform.python_version() -# ) -# -# SYSTEM_VERSION = "{} {}".format( -# platform.system(), -# platform.release() -# ) -# -# INITIAL_SALT = 0x616e67656c696361 -# NET_WORKERS = 1 -# WAIT_TIMEOUT = 15 -# MAX_RETRIES = 5 -# ACKS_THRESHOLD = 8 -# PING_INTERVAL = 5 -# -# notice_displayed = False -# -# BAD_MSG_DESCRIPTION = { -# 16: "[16] msg_id too low, the client time has to be synchronized", -# 17: "[17] msg_id too high, the client time has to be synchronized", -# 18: "[18] incorrect two lower order msg_id bits, the server expects client message msg_id to be divisible by 4", -# 19: "[19] container msg_id is the same as msg_id of a previously received message", -# 20: "[20] message too old, it cannot be verified by the server", -# 32: "[32] msg_seqno too low", -# 33: "[33] msg_seqno too high", -# 34: "[34] an even msg_seqno expected, but odd received", -# 35: "[35] odd msg_seqno expected, but even received", -# 48: "[48] incorrect server salt", -# 64: "[64] invalid container" -# } -# -# def __init__(self, -# dc_id: int, -# test_mode: bool, -# proxy: dict, -# auth_key: bytes, -# api_id: int, -# is_cdn: bool = False, -# client: pyrogram = None): -# if not Session.notice_displayed: -# print("Pyrogram v{}, {}".format(__version__, __copyright__)) -# print("Licensed under the terms of the " + __license__, end="\n\n") -# Session.notice_displayed = True -# -# self.dc_id = dc_id -# self.test_mode = test_mode -# self.proxy = proxy -# self.api_id = api_id -# self.is_cdn = is_cdn -# self.client = client -# -# self.connection = None -# -# self.auth_key = auth_key -# self.auth_key_id = sha1(auth_key).digest()[-8:] -# -# self.session_id = Long(MsgId()) -# self.msg_factory = MsgFactory() -# -# self.current_salt = None -# -# self.pending_acks = set() -# -# self.recv_queue = Queue() -# self.results = {} -# -# self.ping_thread = None -# self.ping_thread_event = Event() -# -# self.next_salt_thread = None -# self.next_salt_thread_event = Event() -# -# self.net_worker_list = [] -# -# self.is_connected = Event() -# -# def start(self): -# while True: -# self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy) -# -# try: -# self.connection.connect() -# -# for i in range(self.NET_WORKERS): -# self.net_worker_list.append( -# Thread( -# target=self.net_worker, -# name="NetWorker#{}".format(i + 1) -# ) -# ) -# -# self.net_worker_list[-1].start() -# -# Thread(target=self.recv, name="RecvThread").start() -# -# self.current_salt = FutureSalt(0, 0, self.INITIAL_SALT) -# self.current_salt = FutureSalt(0, 0, self._send(functions.Ping(0)).new_server_salt) -# self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] -# -# self.next_salt_thread = Thread(target=self.next_salt, name="NextSaltThread") -# self.next_salt_thread.start() -# -# if not self.is_cdn: -# self._send( -# functions.InvokeWithLayer( -# layer, -# functions.InitConnection( -# self.api_id, -# self.DEVICE_MODEL, -# self.SYSTEM_VERSION, -# self.APP_VERSION, -# "en", "", "en", -# functions.help.GetConfig(), -# ) -# ) -# ) -# -# self.ping_thread = Thread(target=self.ping, name="PingThread") -# self.ping_thread.start() -# -# log.info("Connection inited: Layer {}".format(layer)) -# except (OSError, TimeoutError, Error): -# self.stop() -# except Exception as e: -# self.stop() -# raise e -# else: -# break -# -# self.is_connected.set() -# -# log.debug("Session started") -# -# def stop(self): -# self.is_connected.clear() -# -# self.ping_thread_event.set() -# self.next_salt_thread_event.set() -# -# if self.ping_thread is not None: -# self.ping_thread.join() -# -# if self.next_salt_thread is not None: -# self.next_salt_thread.join() -# -# self.ping_thread_event.clear() -# self.next_salt_thread_event.clear() -# -# self.connection.close() -# -# for i in range(self.NET_WORKERS): -# self.recv_queue.put(None) -# -# for i in self.net_worker_list: -# i.join() -# -# self.net_worker_list.clear() -# -# for i in self.results.values(): -# i.event.set() -# -# if self.client and callable(self.client.disconnect_handler): -# try: -# self.client.disconnect_handler(self.client) -# except Exception as e: -# log.error(e, exc_info=True) -# -# log.debug("Session stopped") -# -# def restart(self): -# self.stop() -# self.start() -# -# def pack(self, message: Message): -# data = Long(self.current_salt.salt) + self.session_id + message.write() -# padding = urandom(-(len(data) + 12) % 16 + 12) -# -# # 88 = 88 + 0 (outgoing message) -# msg_key_large = sha256(self.auth_key[88: 88 + 32] + data + padding).digest() -# msg_key = msg_key_large[8:24] -# aes_key, aes_iv = KDF(self.auth_key, msg_key, True) -# -# return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) -# -# def unpack(self, b: BytesIO) -> Message: -# assert b.read(8) == self.auth_key_id, b.getvalue() -# -# msg_key = b.read(16) -# aes_key, aes_iv = KDF(self.auth_key, msg_key, False) -# data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv)) -# data.read(8) -# -# # https://core.telegram.org/mtproto/security_guidelines#checking-session-id -# assert data.read(8) == self.session_id -# -# message = Message.read(data) -# -# # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key -# # https://core.telegram.org/mtproto/security_guidelines#checking-message-length -# # 96 = 88 + 8 (incoming message) -# assert msg_key == sha256(self.auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] -# -# # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id -# # TODO: check for lower msg_ids -# assert message.msg_id % 2 != 0 -# -# return message -# -# def net_worker(self): -# name = threading.current_thread().name -# log.debug("{} started".format(name)) -# -# while True: -# packet = self.recv_queue.get() -# -# if packet is None: -# break -# -# try: -# data = self.unpack(BytesIO(packet)) -# -# messages = ( -# data.body.messages -# if isinstance(data.body, MsgContainer) -# else [data] -# ) -# -# log.debug(data) -# -# for msg in messages: -# if msg.seq_no % 2 != 0: -# if msg.msg_id in self.pending_acks: -# continue -# else: -# self.pending_acks.add(msg.msg_id) -# -# if isinstance(msg.body, (types.MsgDetailedInfo, types.MsgNewDetailedInfo)): -# self.pending_acks.add(msg.body.answer_msg_id) -# continue -# -# if isinstance(msg.body, types.NewSessionCreated): -# continue -# -# msg_id = None -# -# if isinstance(msg.body, (types.BadMsgNotification, types.BadServerSalt)): -# msg_id = msg.body.bad_msg_id -# elif isinstance(msg.body, (core.FutureSalts, types.RpcResult)): -# msg_id = msg.body.req_msg_id -# elif isinstance(msg.body, types.Pong): -# msg_id = msg.body.msg_id -# else: -# if self.client is not None: -# self.client.updates_queue.put(msg.body) -# -# if msg_id in self.results: -# self.results[msg_id].value = getattr(msg.body, "result", msg.body) -# self.results[msg_id].event.set() -# -# if len(self.pending_acks) >= self.ACKS_THRESHOLD: -# log.info("Send {} acks".format(len(self.pending_acks))) -# -# try: -# self._send(types.MsgsAck(list(self.pending_acks)), False) -# except (OSError, TimeoutError): -# pass -# else: -# self.pending_acks.clear() -# except Exception as e: -# log.error(e, exc_info=True) -# -# log.debug("{} stopped".format(name)) -# -# def ping(self): -# log.debug("PingThread started") -# -# while True: -# self.ping_thread_event.wait(self.PING_INTERVAL) -# -# if self.ping_thread_event.is_set(): -# break -# -# try: -# self._send(functions.PingDelayDisconnect( -# 0, self.WAIT_TIMEOUT + 10 -# ), False) -# except (OSError, TimeoutError, Error): -# pass -# -# log.debug("PingThread stopped") -# -# def next_salt(self): -# log.debug("NextSaltThread started") -# -# while True: -# now = datetime.now() -# -# # Seconds to wait until middle-overlap, which is -# # 15 minutes before/after the current/next salt end/start time -# dt = (self.current_salt.valid_until - now).total_seconds() - 900 -# -# log.debug("Current salt: {} | Next salt in {:.0f}m {:.0f}s ({})".format( -# self.current_salt.salt, -# dt // 60, -# dt % 60, -# now + timedelta(seconds=dt) -# )) -# -# self.next_salt_thread_event.wait(dt) -# -# if self.next_salt_thread_event.is_set(): -# break -# -# try: -# self.current_salt = self._send(functions.GetFutureSalts(1)).salts[0] -# except (OSError, TimeoutError, Error): -# self.connection.close() -# break -# -# log.debug("NextSaltThread stopped") -# -# def recv(self): -# log.debug("RecvThread started") -# -# while True: -# packet = self.connection.recv() -# -# if packet is None or len(packet) == 4: -# if packet: -# log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) -# -# if self.is_connected.is_set(): -# Thread(target=self.restart, name="RestartThread").start() -# break -# -# self.recv_queue.put(packet) -# -# log.debug("RecvThread stopped") -# -# def _send(self, data: Object, wait_response: bool = True): -# message = self.msg_factory(data) -# msg_id = message.msg_id -# -# if wait_response: -# self.results[msg_id] = Result() -# -# payload = self.pack(message) -# -# try: -# self.connection.send(payload) -# except OSError as e: -# self.results.pop(msg_id, None) -# raise e -# -# if wait_response: -# self.results[msg_id].event.wait(self.WAIT_TIMEOUT) -# result = self.results.pop(msg_id).value -# -# if result is None: -# raise TimeoutError -# elif isinstance(result, types.RpcError): -# Error.raise_it(result, type(data)) -# elif isinstance(result, types.BadMsgNotification): -# raise Exception(self.BAD_MSG_DESCRIPTION.get( -# result.error_code, -# "Error code {}".format(result.error_code) -# )) -# else: -# return result -# -# def send(self, data: Object, retries: int = MAX_RETRIES): -# self.is_connected.wait(self.WAIT_TIMEOUT) -# -# try: -# return self._send(data) -# except (OSError, TimeoutError, InternalServerError) as e: -# if retries == 0: -# raise e from None -# -# (log.warning if retries < 3 else log.info)( -# "{}: {} Retrying {}".format( -# Session.MAX_RETRIES - retries, -# datetime.now(), type(data))) -# -# time.sleep(0.5) -# return self.send(data, retries - 1, timeout) From b588b553584e6f7df77797e594842661c62dda9d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 Sep 2018 00:44:31 +0200 Subject: [PATCH 073/155] Remove old commented (non-async) code from tcp.py --- pyrogram/connection/transport/tcp/tcp.py | 44 ------------------------ 1 file changed, 44 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 9e09a8b1..91f3dd45 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -101,47 +101,3 @@ class TCP: return None return data - -# class TCP(socks.socksocket): -# def __init__(self, proxy: dict): -# super().__init__() -# self.settimeout(10) -# self.proxy_enabled = proxy.get("enabled", False) -# -# if proxy and self.proxy_enabled: -# self.set_proxy( -# proxy_type=socks.SOCKS5, -# addr=proxy.get("hostname", None), -# port=proxy.get("port", None), -# username=proxy.get("username", None), -# password=proxy.get("password", None) -# ) -# -# log.info("Using proxy {}:{}".format( -# proxy.get("hostname", None), -# proxy.get("port", None) -# )) -# -# def close(self): -# try: -# self.shutdown(socket.SHUT_RDWR) -# except OSError: -# pass -# finally: -# super().close() -# -# def recvall(self, length: int) -> bytes or None: -# data = b"" -# -# while len(data) < length: -# try: -# packet = super().recv(length - len(data)) -# except OSError: -# return None -# else: -# if packet: -# data += packet -# else: -# return None -# -# return data From 8ff413c7e71f2b52973def2590e834b334d68408 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 8 Sep 2018 19:30:12 +0200 Subject: [PATCH 074/155] Make get_chat_members_count async --- .../methods/chats/get_chat_members_count.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index efe53c19..11aa5d1f 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class GetChatMembersCount(BaseClient): - def get_chat_members_count(self, chat_id: int or str): + async def get_chat_members_count(self, chat_id: int or str): """Use this method to get the number of members in a chat. Args: @@ -35,19 +35,23 @@ class GetChatMembersCount(BaseClient): :class:`Error ``ValueError``: If a chat_id belongs to user. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - return self.send( + r = await self.send( functions.messages.GetChats( id=[peer.chat_id] ) - ).chats[0].participants_count + ) + + return r.chats[0].participants_count elif isinstance(peer, types.InputPeerChannel): - return self.send( + r = await self.send( functions.channels.GetFullChannel( channel=peer ) - ).full_chat.participants_count + ) + + return r.full_chat.participants_count else: raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) From dbd60765f695d6fa99d81d4a1e4712ba78895108 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 11 Sep 2018 19:39:46 +0200 Subject: [PATCH 075/155] Fix get_me not being properly awaited --- pyrogram/client/methods/users/get_me.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index f191e298..a8aac70f 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -30,10 +30,10 @@ class GetMe(BaseClient): Raises: :class:`Error ` """ - return utils.parse_user( - await self.send( - functions.users.GetFullUser( - types.InputPeerSelf() - ) - ).user + r = await self.send( + functions.users.GetFullUser( + types.InputPeerSelf() + ) ) + + return utils.parse_user(r.user) From 8070bf4cd40af0e953e395fcd439b6dd24466cb7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Sep 2018 19:41:33 +0200 Subject: [PATCH 076/155] Fix bad merge after editing tcp.py --- pyrogram/connection/transport/tcp/tcp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 55e9fea6..62587900 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -17,9 +17,9 @@ # along with Pyrogram. If not, see . import asyncio +import ipaddress import logging import socket -import ipaddress try: import socks @@ -69,7 +69,7 @@ class TCP: log.info("Using proxy {}:{}".format(hostname, port)) else: - super().__init__( + self.socket = socks.socksocket( socket.AF_INET6 if ipv6 else socket.AF_INET ) From ee06907bdabc008b61af8a094373b49d68478e51 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 8 Oct 2018 20:16:04 +0200 Subject: [PATCH 077/155] Make TCPAbridged async --- .../connection/transport/tcp/tcp_abridged.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 5566b179..49bba1c7 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -27,30 +27,30 @@ class TCPAbridged(TCP): def __init__(self, ipv6: bool, proxy: dict): super().__init__(ipv6, proxy) - def connect(self, address: tuple): - super().connect(address) - super().sendall(b"\xef") + async def connect(self, address: tuple): + await super().connect(address) + await super().send(b"\xef") - def sendall(self, data: bytes, *args): + async def send(self, data: bytes, *args): length = len(data) // 4 - super().sendall( + await super().send( (bytes([length]) if length <= 126 else b"\x7f" + length.to_bytes(3, "little")) + data ) - def recvall(self, length: int = 0) -> bytes or None: - length = super().recvall(1) + async def recv(self, length: int = 0) -> bytes or None: + length = await super().recv(1) if length is None: return None if length == b"\x7f": - length = super().recvall(3) + length = await super().recv(3) if length is None: return None - return super().recvall(int.from_bytes(length, "little") * 4) + return await super().recv(int.from_bytes(length, "little") * 4) From 1bf0d931400cd17aa1de694e1326cddadc9af67c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 8 Oct 2018 20:16:44 +0200 Subject: [PATCH 078/155] Make TCPFull async --- pyrogram/connection/transport/tcp/tcp_full.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 8704247b..f6a09953 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -31,34 +31,33 @@ class TCPFull(TCP): self.seq_no = None - def connect(self, address: tuple): - super().connect(address) + async def connect(self, address: tuple): + await super().connect(address) self.seq_no = 0 - def sendall(self, data: bytes, *args): - # 12 = packet_length (4), seq_no (4), crc32 (4) (at the end) + async def send(self, data: bytes, *args): data = pack(" bytes or None: - length = super().recvall(4) + async def recv(self, length: int = 0) -> bytes or None: + length = await super().recv(4) if length is None: return None - packet = super().recvall(unpack(" Date: Mon, 8 Oct 2018 20:17:04 +0200 Subject: [PATCH 079/155] Make TCPAbridgedO async --- .../connection/transport/tcp/tcp_abridged_o.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 91ee8375..c6d38153 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -34,8 +34,8 @@ class TCPAbridgedO(TCP): self.encrypt = None self.decrypt = None - def connect(self, address: tuple): - super().connect(address) + async def connect(self, address: tuple): + await super().connect(address) while True: nonce = bytearray(os.urandom(64)) @@ -53,12 +53,12 @@ class TCPAbridgedO(TCP): nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64] - super().sendall(nonce) + await super().send(nonce) - def sendall(self, data: bytes, *args): + async def send(self, data: bytes, *args): length = len(data) // 4 - super().sendall( + await super().send( AES.ctr256_encrypt( (bytes([length]) if length <= 126 @@ -68,8 +68,8 @@ class TCPAbridgedO(TCP): ) ) - def recvall(self, length: int = 0) -> bytes or None: - length = super().recvall(1) + async def recv(self, length: int = 0) -> bytes or None: + length = await super().recv(1) if length is None: return None @@ -77,14 +77,14 @@ class TCPAbridgedO(TCP): length = AES.ctr256_decrypt(length, *self.decrypt) if length == b"\x7f": - length = super().recvall(3) + length = await super().recv(3) if length is None: return None length = AES.ctr256_decrypt(length, *self.decrypt) - data = super().recvall(int.from_bytes(length, "little") * 4) + data = await super().recv(int.from_bytes(length, "little") * 4) if data is None: return None From 1fc160c5664e75a95dd486299d6968540295ec53 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 8 Oct 2018 20:17:31 +0200 Subject: [PATCH 080/155] Make TCPIntermediateO async --- .../transport/tcp/tcp_intermediate_o.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index f0598d12..3aefe341 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -35,8 +35,8 @@ class TCPIntermediateO(TCP): self.encrypt = None self.decrypt = None - def connect(self, address: tuple): - super().connect(address) + async def connect(self, address: tuple): + await super().connect(address) while True: nonce = bytearray(os.urandom(64)) @@ -54,25 +54,25 @@ class TCPIntermediateO(TCP): nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64] - super().sendall(nonce) + await super().send(nonce) - def sendall(self, data: bytes, *args): - super().sendall( + async def send(self, data: bytes, *args): + await super().send( AES.ctr256_encrypt( pack(" bytes or None: - length = super().recvall(4) + async def recv(self, length: int = 0) -> bytes or None: + length = await super().recv(4) if length is None: return None length = AES.ctr256_decrypt(length, *self.decrypt) - data = super().recvall(unpack(" Date: Mon, 8 Oct 2018 20:17:47 +0200 Subject: [PATCH 081/155] Remove TODO --- pyrogram/connection/connection.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index fe8b5d5d..17ed9495 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -29,7 +29,6 @@ class Connection: MAX_RETRIES = 3 MODES = { - # TODO: Implement other protocols using asyncio 0: TCPFull, 1: TCPAbridged, 2: TCPIntermediate, From d5c2ca2e1df511eac7c1f67216b70df595deb649 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 8 Oct 2018 20:18:20 +0200 Subject: [PATCH 082/155] Use TCPAbridged (async) connection mode --- pyrogram/connection/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 17ed9495..58bf4fdc 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -36,7 +36,7 @@ class Connection: 4: TCPIntermediateO } - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 2): + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 1): self.dc_id = dc_id self.ipv6 = ipv6 self.proxy = proxy From 418eb0b01a77017325db09d6e323277c29852fcb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 16 Oct 2018 12:38:50 +0200 Subject: [PATCH 083/155] Fix asyncio dispatcher --- pyrogram/client/dispatcher/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 095b08ab..61cd8c7c 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -215,7 +215,7 @@ class Dispatcher: ) ) elif isinstance(update, types.UpdateUserStatus): - self.dispatch( + await self.dispatch( pyrogram.Update( user_status=utils.parse_user_status( update.status, update.user_id From 301ba799cf90507dab9d573506f904a79f9657fe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Nov 2018 09:41:49 +0100 Subject: [PATCH 084/155] Fix update_worker not being async --- pyrogram/client/dispatcher/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 26a0011b..38b01fe9 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -108,9 +108,9 @@ class Dispatcher: self.groups[group].remove(handler) - def update_worker(self): + async def update_worker(self): while True: - update = self.updates.get() + update = await self.updates.get() if update is None: break From 7bc4490680afa2f355761051f877de4b8400cd67 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Nov 2018 10:10:26 +0100 Subject: [PATCH 085/155] Rework dispatcher for asyncio --- pyrogram/client/dispatcher/dispatcher.py | 34 +++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 38b01fe9..74ea37fd 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -60,18 +60,23 @@ class Dispatcher: self.updates = asyncio.Queue() self.groups = OrderedDict() + async def message_parser(update, users, chats): + return await utils.parse_messages(self.client, update.message, users, chats), MessageHandler + + async def deleted_messages_parser(update, users, chats): + return utils.parse_deleted_messages(update), DeletedMessagesHandler + + async def callback_query_parser(update, users, chats): + return await utils.parse_callback_query(self.client, update, users), CallbackQueryHandler + + async def user_status_parser(update, users, chats): + return utils.parse_user_status(update.status, update.user_id), UserStatusHandler + Dispatcher.UPDATES = { - Dispatcher.MESSAGE_UPDATES: - lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler), - - Dispatcher.DELETE_MESSAGE_UPDATES: - lambda upd, usr, cht: (utils.parse_deleted_messages(upd), DeletedMessagesHandler), - - Dispatcher.CALLBACK_QUERY_UPDATES: - lambda upd, usr, cht: (utils.parse_callback_query(self.client, upd, usr), CallbackQueryHandler), - - (types.UpdateUserStatus,): - lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler) + Dispatcher.MESSAGE_UPDATES: message_parser, + Dispatcher.DELETE_MESSAGE_UPDATES: deleted_messages_parser, + Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, + (types.UpdateUserStatus,): user_status_parser } Dispatcher.UPDATES = {key: value for key_tuple, value in Dispatcher.UPDATES.items() for key in key_tuple} @@ -125,8 +130,7 @@ class Dispatcher: if parser is None: continue - update, handler_type = parser(update, users, chats) - tasks = [] + update, handler_type = await parser(update, users, chats) for group in self.groups.values(): for handler in group: @@ -142,12 +146,10 @@ class Dispatcher: continue try: - tasks.append(handler.callback(self.client, *args)) + await handler.callback(self.client, *args) except Exception as e: log.error(e, exc_info=True) finally: break - - await asyncio.gather(*tasks) except Exception as e: log.error(e, exc_info=True) From 8dfa80ef61085c0f8f2fc31737b154aea361a98b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 10 Nov 2018 15:29:37 +0100 Subject: [PATCH 086/155] Add missing await keyword --- pyrogram/client/ext/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 3dc78c60..f01a523c 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -896,7 +896,7 @@ async def parse_callback_query(client, update, users): else: peer_id = int("-100" + str(peer.channel_id)) - message = client.get_messages(peer_id, update.msg_id) + message = await client.get_messages(peer_id, update.msg_id) elif isinstance(update, types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( From 40047877fe73d8fd134c5c6495659ac146e15623 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 20 Nov 2018 19:47:41 +0100 Subject: [PATCH 087/155] Add missing await --- pyrogram/client/methods/messages/send_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 772132ce..ebea756a 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -85,7 +85,7 @@ class SendMessage(BaseClient): if isinstance(r, types.UpdateShortSentMessage): return pyrogram_types.Message( message_id=r.id, - chat=pyrogram_types.Chat(id=list(self.resolve_peer(chat_id).__dict__.values())[0], type="private"), + chat=pyrogram_types.Chat(id=list((await self.resolve_peer(chat_id)).__dict__.values())[0], type="private"), text=message, date=r.date, outgoing=r.out, From 6292fe8f86cd3af6bc1ce0ab7b74a9be19d1fe34 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:24:31 +0100 Subject: [PATCH 088/155] Fix progress callbacks in asyncio --- pyrogram/client/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 924c5df7..3c0081a7 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1202,7 +1202,7 @@ class Client(Methods, BaseClient): file_part += 1 if progress: - progress(self, min(file_part * part_size, file_size), file_size, *progress_args) + await progress(self, min(file_part * part_size, file_size), file_size, *progress_args) except Exception as e: log.error(e, exc_info=True) else: @@ -1321,7 +1321,7 @@ class Client(Methods, BaseClient): offset += limit if progress: - progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) + await progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) r = await session.send( functions.upload.GetFile( @@ -1403,7 +1403,7 @@ class Client(Methods, BaseClient): offset += limit if progress: - progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) + await progress(self, min(offset, size) if size != 0 else offset, size, *progress_args) if len(chunk) < limit: break From 17b166e6a683f666b7cb837e1758e018a01b335b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Dec 2018 11:35:53 +0100 Subject: [PATCH 089/155] CallbackQuery must deal with bytes instead of strings --- pyrogram/client/ext/utils.py | 2 +- pyrogram/client/types/bots/callback_query.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index e687ced1..a8b5b07b 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -917,7 +917,7 @@ async def parse_callback_query(client, update, users): message=message, inline_message_id=inline_message_id, chat_instance=str(update.chat_instance), - data=update.data.decode(), + data=update.data, game_short_name=update.game_short_name, client=client ) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 843a9bb8..447d13ea 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -60,7 +60,7 @@ class CallbackQuery(Object): client=None, message=None, inline_message_id: str = None, - data: str = None, + data: bytes = None, game_short_name: str = None ): self._client = client From f5ce49b7b22f3a68560813464aa95c592736f794 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Dec 2018 14:08:29 +0100 Subject: [PATCH 090/155] - Fix small glitches introduced when merging. - Remove typing requirement, asyncio branch already needs Python 3.5+. - Add async_lru as extra requirement because the standard lru_cache doesn't work in asyncio world. --- pyrogram/client/dispatcher/dispatcher.py | 2 +- pyrogram/client/ext/utils.py | 12 ++++++++++++ pyrogram/client/methods/messages/get_messages.py | 2 +- pyrogram/client/methods/users/get_me.py | 4 ++-- pyrogram/client/types/bots/callback_query.py | 4 ++-- pyrogram/client/types/messages_and_media/sticker.py | 9 +++++---- requirements.txt | 2 +- 7 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index e6dcd4b6..8a22ad7e 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -72,7 +72,7 @@ class Dispatcher: self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, - Dispatcher.DELETE_MESSAGE_UPDATES: deleted_messages_parser, + Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, (types.UpdateUserStatus,): user_status_parser } diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 3e67e3cd..cb2dda39 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -16,7 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio +import sys from base64 import b64decode, b64encode +from concurrent.futures.thread import ThreadPoolExecutor from ...api import types @@ -57,6 +60,15 @@ def encode(s: bytes) -> str: return b64encode(r, b"-_").decode().rstrip("=") +async def ainput(prompt: str = ""): + print(prompt, end="", flush=True) + + with ThreadPoolExecutor(1) as executor: + return (await asyncio.get_event_loop().run_in_executor( + executor, sys.stdin.readline + )).rstrip() + + def get_peer_id(input_peer) -> int: return ( input_peer.user_id if isinstance(input_peer, types.InputPeerUser) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index edcad039..b1ee9339 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -78,6 +78,6 @@ class GetMessages(BaseClient): else: rpc = functions.messages.GetMessages(id=ids) - messages = await pyrogram.Messages._parse(self, self.send(rpc)) + messages = await pyrogram.Messages._parse(self, await self.send(rpc)) return messages if is_iterable else messages.messages[0] diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 390c3b2c..11dd657f 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -33,9 +33,9 @@ class GetMe(BaseClient): """ return pyrogram.User._parse( self, - await self.send( + (await self.send( functions.users.GetFullUser( types.InputPeerSelf() ) - ).user + )).user ) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index c3c23333..3c046cf9 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -78,7 +78,7 @@ class CallbackQuery(PyrogramType): self.game_short_name = game_short_name @staticmethod - def _parse(client, callback_query, users) -> "CallbackQuery": + async def _parse(client, callback_query, users) -> "CallbackQuery": message = None inline_message_id = None @@ -92,7 +92,7 @@ class CallbackQuery(PyrogramType): else: peer_id = int("-100" + str(peer.channel_id)) - message = client.get_messages(peer_id, callback_query.msg_id) + message = await client.get_messages(peer_id, callback_query.msg_id) elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 94385681..c84acd23 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from functools import lru_cache from struct import pack +from async_lru import alru_cache + import pyrogram from pyrogram.api import types, functions from pyrogram.api.errors import StickersetInvalid @@ -92,14 +93,14 @@ class Sticker(PyrogramType): # self.mask_position = mask_position @staticmethod - @lru_cache(maxsize=256) + @alru_cache(maxsize=256) async def get_sticker_set_name(send, input_sticker_set_id): try: - return await send( + return (await send( functions.messages.GetStickerSet( types.InputStickerSetID(*input_sticker_set_id) ) - ).set.short_name + )).set.short_name except StickersetInvalid: return None diff --git a/requirements.txt b/requirements.txt index 8f1eea95..ccfa89ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pyaes==1.6.1 pysocks==1.6.8 -typing==3.6.6 \ No newline at end of file +async_lru==1.0.1 \ No newline at end of file From 4d8c76463c4d84464226c11a53e245503dc9c5f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 09:34:08 +0100 Subject: [PATCH 091/155] Add async_generator requirement --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9c6874c..81df21ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ pyaes==1.6.1 pysocks==1.6.8 typing==3.6.6; python_version<"3.5" -async_lru==1.0.1 \ No newline at end of file +async_lru==1.0.1 +async_generator==1.10 \ No newline at end of file From 0bae143d5db5c54208c478875e89079f9d95c039 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 09:37:26 +0100 Subject: [PATCH 092/155] Fix asyncio merge issues --- pyrogram/client/client.py | 2 +- pyrogram/client/methods/chats/get_dialogs.py | 2 +- .../client/methods/chats/iter_chat_members.py | 21 +++++++------ pyrogram/client/methods/chats/iter_dialogs.py | 31 ++++++++++--------- .../client/methods/messages/iter_history.py | 25 ++++++++------- .../types/messages_and_media/messages.py | 2 +- .../client/types/user_and_chats/dialogs.py | 4 +-- 7 files changed, 48 insertions(+), 39 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index fb4552b7..2dacc26e 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -321,7 +321,7 @@ class Client(Methods, BaseClient): raise ConnectionError("Client is already stopped") if self.takeout_id: - self.send(functions.account.FinishTakeoutSession()) + await self.send(functions.account.FinishTakeoutSession()) log.warning("Takeout session {} finished".format(self.takeout_id)) await Syncer.remove(self) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 4d597425..7d2f44e3 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -78,4 +78,4 @@ class GetDialogs(BaseClient): else: break - return pyrogram.Dialogs._parse(self, r) + return await pyrogram.Dialogs._parse(self, r) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index bdd8d117..f521ebc6 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -17,7 +17,9 @@ # along with Pyrogram. If not, see . from string import ascii_lowercase -from typing import Union, Generator +from typing import Union, AsyncGenerator, Optional + +from async_generator import async_generator, yield_ import pyrogram from ...ext import BaseClient @@ -37,11 +39,12 @@ QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) class IterChatMembers(BaseClient): - def iter_chat_members(self, - chat_id: Union[int, str], - limit: int = 0, - query: str = "", - filter: str = Filters.ALL) -> Generator["pyrogram.ChatMember", None, None]: + @async_generator + async def iter_chat_members(self, + chat_id: Union[int, str], + limit: int = 0, + query: str = "", + filter: str = Filters.ALL) -> Optional[AsyncGenerator["pyrogram.ChatMember", None]]: """Use this method to iterate through the members of a chat sequentially. This convenience method does the same as repeatedly calling :meth:`get_chat_members` in a loop, thus saving you @@ -95,13 +98,13 @@ class IterChatMembers(BaseClient): offset = 0 while True: - chat_members = self.get_chat_members( + chat_members = (await self.get_chat_members( chat_id=chat_id, offset=offset, limit=limit, query=q, filter=filter - ).chat_members + )).chat_members if not chat_members: break @@ -114,7 +117,7 @@ class IterChatMembers(BaseClient): if user_id in yielded: continue - yield chat_member + await yield_(chat_member) yielded.add(chat_member.user.id) diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index 6058cd17..712254e9 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -16,30 +16,33 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Generator +from typing import AsyncGenerator, Optional + +from async_generator import async_generator, yield_ import pyrogram from ...ext import BaseClient class IterDialogs(BaseClient): - def iter_dialogs(self, - offset_date: int = 0, - limit: int = 0) -> Generator["pyrogram.Dialog", None, None]: + @async_generator + async def iter_dialogs(self, + limit: int = 0, + offset_date: int = 0) -> Optional[AsyncGenerator["pyrogram.Dialog", None]]: """Use this method to iterate through a user's dialogs sequentially. This convenience method does the same as repeatedly calling :meth:`get_dialogs` in a loop, thus saving you from the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a single call. Args: - offset_date (``int``): - The offset date in Unix time taken from the top message of a :obj:`Dialog`. - Defaults to 0 (most recent dialog). - limit (``str``, *optional*): Limits the number of dialogs to be retrieved. By default, no limit is applied and all dialogs are returned. + offset_date (``int``): + The offset date in Unix time taken from the top message of a :obj:`Dialog`. + Defaults to 0 (most recent dialog). + Returns: A generator yielding :obj:`Dialog ` objects. @@ -50,12 +53,12 @@ class IterDialogs(BaseClient): total = limit or (1 << 31) - 1 limit = min(100, total) - pinned_dialogs = self.get_dialogs( + pinned_dialogs = (await self.get_dialogs( pinned_only=True - ).dialogs + )).dialogs for dialog in pinned_dialogs: - yield dialog + await yield_(dialog) current += 1 @@ -63,10 +66,10 @@ class IterDialogs(BaseClient): return while True: - dialogs = self.get_dialogs( + dialogs = (await self.get_dialogs( offset_date=offset_date, limit=limit - ).dialogs + )).dialogs if not dialogs: return @@ -74,7 +77,7 @@ class IterDialogs(BaseClient): offset_date = dialogs[-1].top_message.date for dialog in dialogs: - yield dialog + await yield_(dialog) current += 1 diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index ab587988..1a977998 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -16,20 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Generator +from typing import Union, Optional, AsyncGenerator + +from async_generator import async_generator, yield_ import pyrogram from ...ext import BaseClient class IterHistory(BaseClient): - def iter_history(self, - chat_id: Union[int, str], - limit: int = 0, - offset: int = 0, - offset_id: int = 0, - offset_date: int = 0, - reverse: bool = False) -> Generator["pyrogram.Message", None, None]: + @async_generator + async def iter_history(self, + chat_id: Union[int, str], + limit: int = 0, + offset: int = 0, + offset_id: int = 0, + offset_date: int = 0, + reverse: bool = False) -> Optional[AsyncGenerator["pyrogram.Message", None]]: """Use this method to iterate through a chat history sequentially. This convenience method does the same as repeatedly calling :meth:`get_history` in a loop, thus saving you from @@ -70,14 +73,14 @@ class IterHistory(BaseClient): limit = min(100, total) while True: - messages = self.get_history( + messages = (await self.get_history( chat_id=chat_id, limit=limit, offset=offset, offset_id=offset_id, offset_date=offset_date, reverse=reverse - ).messages + )).messages if not messages: return @@ -85,7 +88,7 @@ class IterHistory(BaseClient): offset_id = messages[-1].message_id + (1 if reverse else 0) for message in messages: - yield message + await yield_(message) current += 1 diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 5b12da45..a48b6acf 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -65,7 +65,7 @@ class Messages(PyrogramType, Update): parsed_messages = [] for message in messages.messages: - parsed_messages.appen(await Message._parse(client, message, users, chats, replies=0)) + parsed_messages.append(await Message._parse(client, message, users, chats, replies=0)) if replies: messages_with_replies = {i.id: getattr(i, "reply_to_msg_id", None) for i in messages.messages} diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py index 394ddd28..b3c2d773 100644 --- a/pyrogram/client/types/user_and_chats/dialogs.py +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -47,7 +47,7 @@ class Dialogs(PyrogramType): self.dialogs = dialogs @staticmethod - def _parse(client, dialogs) -> "Dialogs": + async def _parse(client, dialogs) -> "Dialogs": users = {i.id: i for i in dialogs.users} chats = {i.id: i for i in dialogs.chats} @@ -66,7 +66,7 @@ class Dialogs(PyrogramType): else: chat_id = int("-100" + str(to_id.channel_id)) - messages[chat_id] = Message._parse(client, message, users, chats) + messages[chat_id] = await Message._parse(client, message, users, chats) return Dialogs( total_count=getattr(dialogs, "count", len(dialogs.dialogs)), From 35096a28c3960e8775d945493d5e9d4aed3ea428 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Jan 2019 22:57:19 +0100 Subject: [PATCH 093/155] Fix asyncio merge --- pyrogram/client/ext/base_client.py | 18 +++++++------- .../methods/bots/get_game_high_scores.py | 14 +++++------ pyrogram/client/methods/bots/send_game.py | 24 +++++++++---------- .../client/methods/bots/set_game_score.py | 22 ++++++++--------- .../types/messages_and_media/message.py | 8 +++++-- 5 files changed, 45 insertions(+), 41 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 45bab738..7d5e7a4b 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -96,29 +96,29 @@ class BaseClient: self.disconnect_handler = None - def send(self, *args, **kwargs): + async def send(self, *args, **kwargs): pass - def resolve_peer(self, *args, **kwargs): + async def resolve_peer(self, *args, **kwargs): pass - def fetch_peers(self, *args, **kwargs): + async def fetch_peers(self, *args, **kwargs): pass - def add_handler(self, *args, **kwargs): + async def add_handler(self, *args, **kwargs): pass - def save_file(self, *args, **kwargs): + async def save_file(self, *args, **kwargs): pass - def get_messages(self, *args, **kwargs): + async def get_messages(self, *args, **kwargs): pass - def get_history(self, *args, **kwargs): + async def get_history(self, *args, **kwargs): pass - def get_dialogs(self, *args, **kwargs): + async def get_dialogs(self, *args, **kwargs): pass - def get_chat_members(self, *args, **kwargs): + async def get_chat_members(self, *args, **kwargs): pass diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index ad4f8b4a..e58bc17e 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -24,10 +24,10 @@ from pyrogram.client.ext import BaseClient class GetGameHighScores(BaseClient): - def get_game_high_scores(self, - user_id: Union[int, str], - chat_id: Union[int, str], - message_id: int = None): + async def get_game_high_scores(self, + user_id: Union[int, str], + chat_id: Union[int, str], + message_id: int = None): """Use this method to get data for high score tables. Args: @@ -56,11 +56,11 @@ class GetGameHighScores(BaseClient): return pyrogram.GameHighScores._parse( self, - self.send( + await self.send( functions.messages.GetGameHighScores( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, - user_id=self.resolve_peer(user_id) + user_id=await self.resolve_peer(user_id) ) ) ) diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index 401a5aa6..0f3593b0 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -24,15 +24,15 @@ from pyrogram.client.ext import BaseClient class SendGame(BaseClient): - def send_game(self, - chat_id: Union[int, str], - game_short_name: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union["pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply"] = None) -> "pyrogram.Message": + async def send_game(self, + chat_id: Union[int, str], + game_short_name: str, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union["pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply"] = None) -> "pyrogram.Message": """Use this method to send a game. Args: @@ -61,9 +61,9 @@ class SendGame(BaseClient): Raises: :class:`Error ` in case of a Telegram RPC error. """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaGame( id=types.InputGameShortName( bot_id=types.InputUserSelf(), @@ -80,7 +80,7 @@ class SendGame(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index e9d20844..f90b7f46 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -24,13 +24,13 @@ from pyrogram.client.ext import BaseClient class SetGameScore(BaseClient): - def set_game_score(self, - user_id: Union[int, str], - score: int, - force: bool = None, - disable_edit_message: bool = None, - chat_id: Union[int, str] = None, - message_id: int = None): + async def set_game_score(self, + user_id: Union[int, str], + score: int, + force: bool = None, + disable_edit_message: bool = None, + chat_id: Union[int, str] = None, + message_id: int = None): # inline_message_id: str = None): TODO Add inline_message_id """Use this method to set the score of the specified user in a game. @@ -68,12 +68,12 @@ class SetGameScore(BaseClient): :class:`Error ` in case of a Telegram RPC error. :class:`BotScoreNotModified` if the new score is not greater than the user's current score in the chat and force is False. """ - r = self.send( + r = await self.send( functions.messages.SetGameScore( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), score=score, id=message_id, - user_id=self.resolve_peer(user_id), + user_id=await self.resolve_peer(user_id), force=force or None, edit_message=not disable_edit_message or None ) @@ -81,7 +81,7 @@ class SetGameScore(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 77281425..f17eac93 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -423,7 +423,7 @@ class Message(PyrogramType, Update): if message.reply_to_msg_id and replies: try: - parsed_message.reply_to_message = client.get_messages( + parsed_message.reply_to_message = await client.get_messages( parsed_message.chat.id, reply_to_message_ids=message.id, replies=0 @@ -912,7 +912,11 @@ class Message(PyrogramType, Update): else: raise ValueError("The message doesn't contain any keyboard") - async def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = None): + async def download(self, + file_name: str = "", + block: bool = True, + progress: callable = None, + progress_args: tuple = None): """Bound method *download* of :obj:`Message `. Use as a shortcut for: From 63cb4b412e77cb95cef611a5f6ab7d099a5acc45 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 13 Jan 2019 11:21:31 +0100 Subject: [PATCH 094/155] Fix PyCharm mess when merged develop into asyncio --- .../client/methods/messages/send_animation.py | 38 +++++++++---------- .../client/methods/messages/send_audio.py | 38 +++++++++---------- .../client/methods/messages/send_document.py | 38 +++++++++---------- .../client/methods/messages/send_photo.py | 38 +++++++++---------- .../client/methods/messages/send_sticker.py | 38 +++++++++---------- .../client/methods/messages/send_video.py | 38 +++++++++---------- .../methods/messages/send_video_note.py | 38 +++++++++---------- .../client/methods/messages/send_voice.py | 38 +++++++++---------- 8 files changed, 152 insertions(+), 152 deletions(-) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 1bf0255b..44d7d137 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -177,25 +177,25 @@ class SendAnimation(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(animation, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(animation, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index ea23f8d3..e333dee4 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -176,25 +176,25 @@ class SendAudio(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(audio, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(audio, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index f60f7815..e9ab9375 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -157,25 +157,25 @@ class SendDocument(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(document, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(document, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index d734ee35..267a7136 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -153,25 +153,25 @@ class SendPhoto(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(photo, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(photo, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 172d7c80..7525e2f9 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -137,25 +137,25 @@ class SendSticker(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - message="" - ) - ) - except FilePartMissing as e: - await self.save_file(sticker, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + message="" ) + ) + except FilePartMissing as e: + await self.save_file(sticker, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index ff6020df..3c202b55 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -180,25 +180,25 @@ class SendVideo(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(video, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(video, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 712c09f1..fbc6c984 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -155,25 +155,25 @@ class SendVideoNote(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - message="" - ) - ) - except FilePartMissing as e: - await self.save_file(video_note, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + message="" ) + ) + except FilePartMissing as e: + await self.save_file(video_note, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 458192b8..8b6c8e61 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -156,25 +156,25 @@ class SendVoice(BaseClient): while True: try: r = await self.send( - functions.messages.SendMedia( - peer=await self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - except FilePartMissing as e: - await self.save_file(voice, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} + functions.messages.SendMedia( + peer=await self.resolve_peer(chat_id), + media=media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + reply_markup=reply_markup.write() if reply_markup else None, + **style.parse(caption) ) + ) + except FilePartMissing as e: + await self.save_file(voice, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return await pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats} + ) except BaseClient.StopTransmission: return None From d72754be1ede4b7c1ffd88a5e1908f634230fff9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 17 Jan 2019 12:30:40 +0100 Subject: [PATCH 095/155] Add missing await --- pyrogram/client/methods/chats/get_chat.py | 2 +- pyrogram/client/types/user_and_chats/chat.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index c868acb5..422cc34d 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -73,4 +73,4 @@ class GetChat(BaseClient): else: r = await self.send(functions.messages.GetFullChat(peer.chat_id)) - return pyrogram.Chat._parse_full(self, r) + return await pyrogram.Chat._parse_full(self, r) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index ec30b866..de1cd633 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -174,7 +174,7 @@ class Chat(PyrogramType): return Chat._parse_channel_chat(client, chats[peer.channel_id]) @staticmethod - def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": + async def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": if isinstance(chat_full, types.UserFull): parsed_chat = Chat._parse_user_chat(client, chat_full.user) parsed_chat.description = chat_full.about @@ -200,7 +200,7 @@ class Chat(PyrogramType): parsed_chat.sticker_set_name = full_chat.stickerset if full_chat.pinned_msg_id: - parsed_chat.pinned_message = client.get_messages( + parsed_chat.pinned_message = await client.get_messages( parsed_chat.id, message_ids=full_chat.pinned_msg_id ) From 652b3f90bc8170a41b04b589d147d366a7858424 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 17 Jan 2019 12:34:30 +0100 Subject: [PATCH 096/155] Remove async from some method signatures. They are not asynchronous --- pyrogram/client/ext/base_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 15d7637b..d33b5bb9 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -105,10 +105,10 @@ class BaseClient: async def resolve_peer(self, *args, **kwargs): pass - async def fetch_peers(self, *args, **kwargs): + def fetch_peers(self, *args, **kwargs): pass - async def add_handler(self, *args, **kwargs): + def add_handler(self, *args, **kwargs): pass async def save_file(self, *args, **kwargs): From e83012bfb844d55c1386f5281a6edc0d75766ffc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Jan 2019 10:24:04 +0100 Subject: [PATCH 097/155] Add missing await keywords --- pyrogram/client/ext/base_client.py | 2 +- pyrogram/client/methods/chats/iter_chat_members.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 80b719af..d7414530 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -126,5 +126,5 @@ class BaseClient: async def get_chat_members(self, *args, **kwargs): pass - def get_chat_members_count(self, *args, **kwargs): + async def get_chat_members_count(self, *args, **kwargs): pass diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index d1ac8d6a..ce923b65 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -88,7 +88,7 @@ class IterChatMembers(BaseClient): filter = ( Filters.RECENT - if self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL + if await self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL else filter ) From 58cb30d97cf5b2ea57f2d809f5d9273fa0c7e82c Mon Sep 17 00:00:00 2001 From: MBRCTV <39084010+MBRCTV@users.noreply.github.com> Date: Tue, 29 Jan 2019 16:36:21 -0500 Subject: [PATCH 098/155] Added missing 'await' on thumb --- pyrogram/client/methods/messages/send_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 3c202b55..aecffe28 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -133,7 +133,7 @@ class SendVideo(BaseClient): try: if os.path.exists(video): - thumb = None if thumb is None else self.save_file(thumb) + thumb = None if thumb is None else await self.save_file(thumb) file = await self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], From cc7cb27858255a465fcf46babe48d4bb91b4498b Mon Sep 17 00:00:00 2001 From: MBRCTV <39084010+MBRCTV@users.noreply.github.com> Date: Wed, 30 Jan 2019 09:45:30 -0500 Subject: [PATCH 099/155] Add missing await for send_audio thumbnail upload (#210) --- pyrogram/client/methods/messages/send_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e333dee4..73208b25 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -130,7 +130,7 @@ class SendAudio(BaseClient): try: if os.path.exists(audio): - thumb = None if thumb is None else self.save_file(thumb) + thumb = None if thumb is None else await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), From 4eb26c5b9276894b30d4025b14ff22609fcc5609 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 4 Feb 2019 18:34:58 +0100 Subject: [PATCH 100/155] Fix sleep method calls in asyncio: time.sleep -> asyncio.sleep --- pyrogram/client/client.py | 7 +++---- pyrogram/client/methods/chats/get_dialogs.py | 6 +++--- pyrogram/client/methods/messages/get_history.py | 2 +- pyrogram/client/methods/messages/get_messages.py | 4 ++-- pyrogram/session/auth.py | 3 ++- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 863856f3..267aade4 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -48,7 +48,6 @@ from pyrogram.api.errors import ( VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied, PasswordRecoveryNa, PasswordEmpty ) -from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers.handler import Handler from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES @@ -571,7 +570,7 @@ class Client(Methods, BaseClient): raise else: print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise @@ -707,7 +706,7 @@ class Client(Methods, BaseClient): raise else: print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) self.password = None self.recovery_code = None except Exception as e: @@ -721,7 +720,7 @@ class Client(Methods, BaseClient): raise else: print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 7d2f44e3..aa6ca912 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import time import pyrogram from pyrogram.api import functions, types @@ -73,8 +73,8 @@ class GetDialogs(BaseClient): ) ) except FloodWait as e: - log.warning("Sleeping {}s".format(e.x)) - time.sleep(e.x) + log.warning("Sleeping for {}s".format(e.x)) + await asyncio.sleep(e.x) else: break diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index ae9925eb..88e69244 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import time from typing import Union import pyrogram diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 09a132b6..a7b9b751 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -import time from typing import Union, Iterable import pyrogram @@ -88,7 +88,7 @@ class GetMessages(BaseClient): r = await self.send(rpc) except FloodWait as e: log.warning("Sleeping for {}s".format(e.x)) - time.sleep(e.x) + await asyncio.sleep(e.x) else: break diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 17b22d6f..f966705f 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging import time from hashlib import sha1 @@ -254,7 +255,7 @@ class Auth: else: raise e - time.sleep(1) + await asyncio.sleep(1) continue else: return auth_key From bd56c428c632d3f44d0a1b3df8842dc074092bbd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Feb 2019 12:09:27 +0100 Subject: [PATCH 101/155] Inherit from StopAsyncIteration --- pyrogram/client/types/update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 2ec22f5a..37307111 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -17,11 +17,11 @@ # along with Pyrogram. If not, see . -class StopPropagation(StopIteration): +class StopPropagation(StopAsyncIteration): pass -class ContinuePropagation(StopIteration): +class ContinuePropagation(StopAsyncIteration): pass From 99af3a4180077518d1115e43bee59f92fbb48529 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Mar 2019 17:11:55 +0100 Subject: [PATCH 102/155] Tune upload pool size and workers count Use 1 worker only in case of small files --- pyrogram/client/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f58d3ceb..5181b029 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1447,11 +1447,13 @@ class Client(Methods, BaseClient): file_total_parts = int(math.ceil(file_size / part_size)) is_big = file_size > 10 * 1024 * 1024 + pool_size = 3 if is_big else 1 + workers_count = 4 if is_big else 1 is_missing_part = file_id is not None file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - pool = [Session(self, self.dc_id, self.auth_key, is_media=True) for _ in range(3)] - workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(4)] + pool = [Session(self, self.dc_id, self.auth_key, is_media=True) for _ in range(pool_size)] + workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: From 2078e6da282d29320aa7336f2a5fdca8b0cb1b01 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Mar 2019 21:27:25 +0100 Subject: [PATCH 103/155] Turn send_cached_media async --- pyrogram/client/methods/messages/send_cached_media.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 843b7197..afcde68d 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -27,7 +27,7 @@ from pyrogram.client.ext import BaseClient, utils class SendCachedMedia(BaseClient): - def send_cached_media( + async def send_cached_media( self, chat_id: Union[int, str], file_id: str, @@ -114,9 +114,9 @@ class SendCachedMedia(BaseClient): ) ) - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -128,7 +128,7 @@ class SendCachedMedia(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats} From 3d23b681e3387daffa1f95aa684cbd04649f4b77 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Mar 2019 16:48:34 +0100 Subject: [PATCH 104/155] Add missing await --- pyrogram/client/methods/chats/iter_chat_members.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index 5b91e81c..f9e3294f 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -86,7 +86,7 @@ class IterChatMembers(BaseClient): queries = [query] if query else QUERIES total = limit or (1 << 31) - 1 limit = min(200, total) - resolved_chat_id = self.resolve_peer(chat_id) + resolved_chat_id = await self.resolve_peer(chat_id) filter = ( Filters.RECENT From a329e56259484cdf79b061a9e56a3112a5c9a9ad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 19:56:04 +0100 Subject: [PATCH 105/155] Fix import order causing errors --- pyrogram/api/errors/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/api/errors/__init__.py b/pyrogram/api/errors/__init__.py index 8a1dc699..ca65619c 100644 --- a/pyrogram/api/errors/__init__.py +++ b/pyrogram/api/errors/__init__.py @@ -16,5 +16,5 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .error import UnknownError from .exceptions import * +from .error import UnknownError From a06885dd14956029c76e4554b122829dbc068b48 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Mar 2019 19:56:25 +0100 Subject: [PATCH 106/155] Add support for "async with" context manager --- pyrogram/client/client.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index bf0401da..0327615a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -243,6 +243,12 @@ class Client(Methods, BaseClient): def __exit__(self, *args): self.stop() + async def __aenter__(self): + return await self.start() + + async def __aexit__(self, *args): + await self.stop() + @property def proxy(self): return self._proxy From ac318831dc0feb6151e63da1bf2ff28ed8ed036a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 22 Mar 2019 13:47:31 +0100 Subject: [PATCH 107/155] Add missing awaits --- pyrogram/client/ext/base_client.py | 2 +- pyrogram/client/methods/bots/answer_inline_query.py | 4 ++-- pyrogram/client/types/inline_mode/inline_query.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 1e10a7b3..34328589 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -129,5 +129,5 @@ class BaseClient: async def get_chat_members_count(self, *args, **kwargs): pass - def answer_inline_query(self, *args, **kwargs): + async def answer_inline_query(self, *args, **kwargs): pass diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 7b3524b2..65f2ff3a 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -24,7 +24,7 @@ from ...types.inline_mode import InlineQueryResult class AnswerInlineQuery(BaseClient): - def answer_inline_query( + async def answer_inline_query( self, inline_query_id: str, results: List[InlineQueryResult], @@ -75,7 +75,7 @@ class AnswerInlineQuery(BaseClient): Returns: On success, True is returned. """ - return self.send( + return await self.send( functions.messages.SetInlineBotResults( query_id=int(inline_query_id), results=[r.write() for r in results], diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 9c1c02ac..737960ca 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -83,7 +83,7 @@ class InlineQuery(PyrogramType, Update): client=client ) - def answer( + async def answer( self, results: List[InlineQueryResult], cache_time: int = 300, @@ -141,7 +141,7 @@ class InlineQuery(PyrogramType, Update): where they wanted to use the bot's inline capabilities. """ - return self._client.answer_inline_query( + return await self._client.answer_inline_query( inline_query_id=self.id, results=results, cache_time=cache_time, From 7f7f9768fd9c150b2ba8db72228455a64c6fe25f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Mar 2019 14:59:55 +0100 Subject: [PATCH 108/155] Add missing awaits --- pyrogram/client/client.py | 6 +++--- pyrogram/client/methods/messages/edit_message_caption.py | 2 +- pyrogram/client/methods/messages/edit_message_media.py | 2 +- pyrogram/client/methods/messages/edit_message_text.py | 2 +- pyrogram/client/methods/messages/send_animation.py | 2 +- pyrogram/client/methods/messages/send_audio.py | 2 +- pyrogram/client/methods/messages/send_cached_media.py | 2 +- pyrogram/client/methods/messages/send_document.py | 2 +- pyrogram/client/methods/messages/send_media_group.py | 2 +- pyrogram/client/methods/messages/send_message.py | 2 +- pyrogram/client/methods/messages/send_photo.py | 2 +- pyrogram/client/methods/messages/send_video.py | 2 +- pyrogram/client/methods/messages/send_voice.py | 2 +- pyrogram/client/style/html.py | 6 +++--- pyrogram/client/style/markdown.py | 4 ++-- 15 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 6f654099..bda5f437 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1362,7 +1362,7 @@ class Client(Methods, BaseClient): if peer_id > 0: self.fetch_peers( - self.send( + await self.send( functions.users.GetUsers( id=[types.InputUser(user_id=peer_id, access_hash=0)] ) @@ -1370,13 +1370,13 @@ class Client(Methods, BaseClient): ) else: if str(peer_id).startswith("-100"): - self.send( + await self.send( functions.channels.GetChannels( id=[types.InputChannel(channel_id=int(str(peer_id)[4:]), access_hash=0)] ) ) else: - self.send( + await self.send( functions.messages.GetChats( id=[-peer_id] ) diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 22e090fc..8fd89dc6 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -67,7 +67,7 @@ class EditMessageCaption(BaseClient): peer=await self.resolve_peer(chat_id), id=message_id, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 2d9aa23b..a5ae56fe 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -353,7 +353,7 @@ class EditMessageMedia(BaseClient): id=message_id, reply_markup=reply_markup.write() if reply_markup else None, media=media, - **style.parse(caption) + **await style.parse(caption) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index b37255a2..fac74f89 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -72,7 +72,7 @@ class EditMessageText(BaseClient): id=message_id, no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) + **await style.parse(text) ) ) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 507cf6f9..ffe70fd9 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -187,7 +187,7 @@ class SendAnimation(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index ea3a0ac4..f620b25b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -186,7 +186,7 @@ class SendAudio(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 9511c548..b9f00495 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -122,7 +122,7 @@ class SendCachedMedia(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 123a79fc..ebdae534 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -167,7 +167,7 @@ class SendDocument(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 40c53066..32b6af59 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -183,7 +183,7 @@ class SendMediaGroup(BaseClient): types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **style.parse(i.caption) + **await style.parse(i.caption) ) ) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 7c36800e..090bd63c 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -76,7 +76,7 @@ class SendMessage(BaseClient): :class:`RPCError ` in case of a Telegram RPC error. """ style = self.html if parse_mode.lower() == "html" else self.markdown - message, entities = style.parse(text).values() + message, entities = (await style.parse(text)).values() r = await self.send( functions.messages.SendMessage( diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 5686dfdc..00302233 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -164,7 +164,7 @@ class SendPhoto(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 00a717e4..a9c72df8 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -190,7 +190,7 @@ class SendVideo(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 588bb446..3d8bfaf2 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -166,7 +166,7 @@ class SendVoice(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **await style.parse(caption) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py index 9c0a372c..d9aec531 100644 --- a/pyrogram/client/style/html.py +++ b/pyrogram/client/style/html.py @@ -40,9 +40,9 @@ class HTML: def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - def parse(self, message: str): - entities = [] + async def parse(self, message: str): message = utils.add_surrogates(str(message or "")) + entities = [] offset = 0 for match in self.HTML_RE.finditer(message): @@ -56,7 +56,7 @@ class HTML: user_id = int(mention.group(1)) try: - input_user = self.client.resolve_peer(user_id) + input_user = await self.client.resolve_peer(user_id) except PeerIdInvalid: input_user = None diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py index adb86e94..1174c639 100644 --- a/pyrogram/client/style/markdown.py +++ b/pyrogram/client/style/markdown.py @@ -57,7 +57,7 @@ class Markdown: def __init__(self, client: "pyrogram.BaseClient" = None): self.client = client - def parse(self, message: str): + async def parse(self, message: str): message = utils.add_surrogates(str(message or "")).strip() entities = [] offset = 0 @@ -73,7 +73,7 @@ class Markdown: user_id = int(mention.group(1)) try: - input_user = self.client.resolve_peer(user_id) + input_user = await self.client.resolve_peer(user_id) except PeerIdInvalid: input_user = None From 29940fbc662d5906977a8718ca98a62d61e1bec3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Mar 2019 15:44:29 +0100 Subject: [PATCH 109/155] Fix StopTransmission in asyncio by inheriting from StopAsyncIteration Instead of StopIteration --- pyrogram/client/ext/base_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 7e9f51a1..ae05c1f2 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -26,7 +26,7 @@ from ...session.internals import MsgId class BaseClient: - class StopTransmission(StopIteration): + class StopTransmission(StopAsyncIteration): pass APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__) From 95a7befed50d8094b95e12d29c2c95301c0f3b8c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 8 Apr 2019 16:50:48 +0200 Subject: [PATCH 110/155] Update async version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d1e195fa..962b8782 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,7 +18,7 @@ import sys -__version__ = "0.12.0.develop" +__version__ = "0.12.0.async" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan Tès ".replace( "\xe8", "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" From ad49e72f02757618d51d89dfd518370bf48f579f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 13 Apr 2019 17:32:18 +0200 Subject: [PATCH 111/155] Fix inline_query_parser in asyncio branch --- pyrogram/client/ext/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 8ecb0929..f1358b86 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -74,7 +74,7 @@ class Dispatcher: return pyrogram.UserStatus._parse(self.client, update.status, update.user_id), UserStatusHandler async def inline_query_parser(update, users, chats): - return pyrogram.InlineQuery._parse(self.client, update.status, update.user_id), UserStatusHandler + return pyrogram.InlineQuery._parse(self.client, update, users), InlineQueryHandler self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, From 1750300ab93ea04d3058b51e8843f080eb18671d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 13 Apr 2019 17:51:47 +0200 Subject: [PATCH 112/155] Add missing awaits --- pyrogram/client/methods/bots/answer_inline_query.py | 7 ++++++- pyrogram/client/types/inline_mode/inline_query_result.py | 2 +- .../types/inline_mode/inline_query_result_article.py | 4 ++-- .../input_message_content/input_text_message_content.py | 4 ++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 65f2ff3a..88a661d0 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -75,10 +75,15 @@ class AnswerInlineQuery(BaseClient): Returns: On success, True is returned. """ + written_results = [] # Py 3.5 doesn't support await inside comprehensions + + for r in results: + written_results.append(await r.write()) + return await self.send( functions.messages.SetInlineBotResults( query_id=int(inline_query_id), - results=[r.write() for r in results], + results=written_results, cache_time=cache_time, gallery=None, private=is_personal or None, diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index 3e7fcb02..6fd1975d 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -55,5 +55,5 @@ class InlineQueryResult(PyrogramType): self.type = type self.id = id - def write(self): + async def write(self): pass diff --git a/pyrogram/client/types/inline_mode/inline_query_result_article.py b/pyrogram/client/types/inline_mode/inline_query_result_article.py index 8d0089c3..3f0c2997 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -84,11 +84,11 @@ class InlineQueryResultArticle(InlineQueryResult): self.thumb_width = thumb_width self.thumb_height = thumb_height - def write(self): + async def write(self): return types.InputBotInlineResult( id=str(self.id), type=self.type, - send_message=self.input_message_content.write(self.reply_markup), + send_message=await self.input_message_content.write(self.reply_markup), title=self.title, description=self.description, url=self.url, diff --git a/pyrogram/client/types/input_message_content/input_text_message_content.py b/pyrogram/client/types/input_message_content/input_text_message_content.py index 0e6ffa8b..dda1f3f3 100644 --- a/pyrogram/client/types/input_message_content/input_text_message_content.py +++ b/pyrogram/client/types/input_message_content/input_text_message_content.py @@ -46,9 +46,9 @@ class InputTextMessageContent(InputMessageContent): self.parse_mode = parse_mode self.disable_web_page_preview = disable_web_page_preview - def write(self, reply_markup): + async def write(self, reply_markup): return types.InputBotInlineMessageText( no_webpage=self.disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **(HTML() if self.parse_mode.lower() == "html" else Markdown()).parse(self.message_text) + **await(HTML() if self.parse_mode.lower() == "html" else Markdown()).parse(self.message_text) ) From 1dd3ba4133258c81b8cbcee37b35bfb60d9e55d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Apr 2019 18:47:45 +0200 Subject: [PATCH 113/155] Add missing awaits --- pyrogram/client/methods/users/set_user_profile_photo.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py index af02a12d..5a155b94 100644 --- a/pyrogram/client/methods/users/set_user_profile_photo.py +++ b/pyrogram/client/methods/users/set_user_profile_photo.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class SetUserProfilePhoto(BaseClient): - def set_user_profile_photo( + async def set_user_profile_photo( self, photo: str ) -> bool: @@ -43,9 +43,9 @@ class SetUserProfilePhoto(BaseClient): """ return bool( - self.send( + await self.send( functions.photos.UploadProfilePhoto( - file=self.save_file(photo) + file=await self.save_file(photo) ) ) ) From 8dd99a868378d7b1109342fb0d71d01560ce90ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20G=C3=B6tzer?= Date: Tue, 30 Apr 2019 11:49:18 +0200 Subject: [PATCH 114/155] Use str or bytes for callback_data and CallbackQuery.data (#241) --- pyrogram/client/types/bots/callback_query.py | 2 +- pyrogram/client/types/bots/inline_keyboard_button.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 30a5333f..767d768c 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -79,7 +79,7 @@ class CallbackQuery(PyrogramType, Update): self.chat_instance = chat_instance self.message = message self.inline_message_id = inline_message_id - self.data = data + self.data: str = str(data, "utf-8") self.game_short_name = game_short_name @staticmethod diff --git a/pyrogram/client/types/bots/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py index c0c3eb8c..5e225846 100644 --- a/pyrogram/client/types/bots/inline_keyboard_button.py +++ b/pyrogram/client/types/bots/inline_keyboard_button.py @@ -15,6 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union from pyrogram.api.types import ( KeyboardButtonUrl, KeyboardButtonCallback, @@ -61,7 +62,7 @@ class InlineKeyboardButton(PyrogramType): def __init__( self, text: str, - callback_data: bytes = None, + callback_data: Union[str, bytes] = None, url: str = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None, @@ -71,7 +72,7 @@ class InlineKeyboardButton(PyrogramType): self.text = str(text) self.url = url - self.callback_data = callback_data + self.callback_data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data self.switch_inline_query = switch_inline_query self.switch_inline_query_current_chat = switch_inline_query_current_chat self.callback_game = callback_game From ec258312dd5af2958d845adf202060493f0638d8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 3 May 2019 22:47:51 +0200 Subject: [PATCH 115/155] Add missing awaits --- pyrogram/client/methods/chats/update_chat_username.py | 6 +++--- pyrogram/client/methods/users/update_username.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index 39cdfaeb..12f5fe12 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class UpdateChatUsername(BaseClient): - def update_chat_username( + async def update_chat_username( self, chat_id: Union[int, str], username: Union[str, None] @@ -46,11 +46,11 @@ class UpdateChatUsername(BaseClient): ``ValueError`` if a chat_id belongs to a user or chat. """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): return bool( - self.send( + await self.send( functions.channels.UpdateUsername( channel=peer, username=username or "" diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index d0c87eb2..15877992 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class UpdateUsername(BaseClient): - def update_username( + async def update_username( self, username: Union[str, None] ) -> bool: @@ -45,7 +45,7 @@ class UpdateUsername(BaseClient): """ return bool( - self.send( + await self.send( functions.account.UpdateUsername( username=username or "" ) From a6198921c3ec66fc53b6921b991b9739071679ac Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 3 May 2019 22:55:00 +0200 Subject: [PATCH 116/155] Fix an unresolved reference --- pyrogram/client/methods/messages/delete_messages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 9dc4cf39..9eb42867 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -57,14 +57,14 @@ class DeleteMessages(BaseClient): message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] if isinstance(peer, types.InputPeerChannel): - await self.send( + r = await self.send( functions.channels.DeleteMessages( channel=peer, id=message_ids ) ) else: - await self.send( + r = await self.send( functions.messages.DeleteMessages( id=message_ids, revoke=revoke or None From 762ea3e62ef6de512c7df74511196aef9be25925 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 May 2019 17:39:57 +0200 Subject: [PATCH 117/155] Add an hint about which client is loading the plugins --- pyrogram/client/client.py | 40 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b1366b26..ce8ae2fd 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -53,8 +53,8 @@ from pyrogram.errors import ( PasswordRecoveryNa, PasswordEmpty ) from pyrogram.session import Auth, Session -from .ext.utils import ainput from .ext import utils, Syncer, BaseClient, Dispatcher +from .ext.utils import ainput from .methods import Methods log = logging.getLogger(__name__) @@ -1181,8 +1181,8 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) - log.info('[LOAD] {}("{}") in group {} from "{}"'.format( - type(handler).__name__, name, group, module_path)) + log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( + self.session_name, type(handler).__name__, name, group, module_path)) count += 1 except Exception: @@ -1195,11 +1195,13 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) except ImportError: - log.warning('[LOAD] Ignoring non-existent module "{}"'.format(module_path)) + log.warning('[{}] [LOAD] Ignoring non-existent module "{}"'.format( + self.session_name, module_path)) continue if "__path__" in dir(module): - log.warning('[LOAD] Ignoring namespace "{}"'.format(module_path)) + log.warning('[{}] [LOAD] Ignoring namespace "{}"'.format( + self.session_name, module_path)) continue if handlers is None: @@ -1214,14 +1216,14 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) - log.info('[LOAD] {}("{}") in group {} from "{}"'.format( - type(handler).__name__, name, group, module_path)) + log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( + self.session_name, type(handler).__name__, name, group, module_path)) count += 1 except Exception: if warn_non_existent_functions: - log.warning('[LOAD] Ignoring non-existent function "{}" from "{}"'.format( - name, module_path)) + log.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format( + self.session_name, name, module_path)) if exclude is not None: for path, handlers in exclude: @@ -1231,11 +1233,13 @@ class Client(Methods, BaseClient): try: module = import_module(module_path) except ImportError: - log.warning('[UNLOAD] Ignoring non-existent module "{}"'.format(module_path)) + log.warning('[{}] [UNLOAD] Ignoring non-existent module "{}"'.format( + self.session_name, module_path)) continue if "__path__" in dir(module): - log.warning('[UNLOAD] Ignoring namespace "{}"'.format(module_path)) + log.warning('[{}] [UNLOAD] Ignoring namespace "{}"'.format( + self.session_name, module_path)) continue if handlers is None: @@ -1250,19 +1254,21 @@ class Client(Methods, BaseClient): if isinstance(handler, Handler) and isinstance(group, int): self.remove_handler(handler, group) - log.info('[UNLOAD] {}("{}") from group {} in "{}"'.format( - type(handler).__name__, name, group, module_path)) + log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format( + self.session_name, type(handler).__name__, name, group, module_path)) count -= 1 except Exception: if warn_non_existent_functions: - log.warning('[UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( - name, module_path)) + log.warning('[{}] [UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( + self.session_name, name, module_path)) if count > 0: - log.warning('Successfully loaded {} plugin{} from "{}"'.format(count, "s" if count > 1 else "", root)) + log.warning('[{}] Successfully loaded {} plugin{} from "{}"'.format( + self.session_name, count, "s" if count > 1 else "", root)) else: - log.warning('No plugin loaded from "{}"'.format(root)) + log.warning('[{}] No plugin loaded from "{}"'.format( + self.session_name, root)) def save_session(self): auth_key = base64.b64encode(self.auth_key).decode() From b3d6b41ca8fda79c60b43d5dfb214ab0fa20a8df Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 Jun 2019 11:28:29 +0200 Subject: [PATCH 118/155] Fix Syncer not creating Event and Lock objects inside the current loop --- pyrogram/client/ext/syncer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index a9fc4221..c5f15c1a 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -33,11 +33,17 @@ class Syncer: INTERVAL = 20 clients = {} - event = asyncio.Event() - lock = asyncio.Lock() + event = None + lock = None @classmethod async def add(cls, client): + if cls.event is None: + cls.event = asyncio.Event() + + if cls.lock is None: + cls.lock = asyncio.Lock() + with await cls.lock: cls.sync(client) From 9bd9d7797b0e6bc1f29563506e7749f5cbf8ccdd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 Jun 2019 11:58:29 +0200 Subject: [PATCH 119/155] Replace "with await" with "async with" --- pyrogram/client/client.py | 4 ++-- pyrogram/client/ext/syncer.py | 6 +++--- pyrogram/connection/transport/tcp/tcp.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b6241746..d81c3707 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1555,7 +1555,7 @@ class Client(Methods, BaseClient): is_big: bool, progress: callable, progress_args: tuple = ()) -> str: - with await self.media_sessions_lock: + async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: @@ -1670,7 +1670,7 @@ class Client(Methods, BaseClient): ) elif isinstance(r, types.upload.FileCdnRedirect): - with await self.media_sessions_lock: + async with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index c5f15c1a..88caa160 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -44,7 +44,7 @@ class Syncer: if cls.lock is None: cls.lock = asyncio.Lock() - with await cls.lock: + async with cls.lock: cls.sync(client) cls.clients[id(client)] = client @@ -54,7 +54,7 @@ class Syncer: @classmethod async def remove(cls, client): - with await cls.lock: + async with cls.lock: cls.sync(client) del cls.clients[id(client)] @@ -77,7 +77,7 @@ class Syncer: try: await asyncio.wait_for(cls.event.wait(), cls.INTERVAL) except asyncio.TimeoutError: - with await cls.lock: + async with cls.lock: for client in cls.clients.values(): cls.sync(client) else: diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 237c5c59..0d33fcd9 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -92,7 +92,7 @@ class TCP: self.socket.close() async def send(self, data: bytes): - with await self.lock: + async with self.lock: self.writer.write(data) await self.writer.drain() From 2ba445d21e2dff947e7d1e7d0fcfd71b597cf306 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:48:35 +0200 Subject: [PATCH 120/155] Fix asyncio lock not being awaited properly --- pyrogram/client/ext/dispatcher.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 92b1d640..a2a81495 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -114,19 +114,25 @@ class Dispatcher: log.info("Stopped {} UpdateWorkerTasks".format(self.workers)) def add_handler(self, handler, group: int): - with self.lock: - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) + async def fn(): + async with self.lock: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) - self.groups[group].append(handler) + self.groups[group].append(handler) + + asyncio.get_event_loop().run_until_complete(fn()) def remove_handler(self, handler, group: int): - with self.lock: - if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + async def fn(): + async with self.lock: + if group not in self.groups: + raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) - self.groups[group].remove(handler) + self.groups[group].remove(handler) + + asyncio.get_event_loop().run_until_complete(fn()) async def update_worker(self): while True: @@ -145,7 +151,7 @@ class Dispatcher: else (None, type(None)) ) - with self.lock: + async with self.lock: for group in self.groups.values(): for handler in group: args = None From 633e11531a882d0cd9cafdf437f9e99e43a21271 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 13:56:12 +0200 Subject: [PATCH 121/155] Fix coroutine scheduling when adding/removing handlers --- pyrogram/client/ext/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index c2c35569..167e9d67 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -130,7 +130,7 @@ class Dispatcher: for lock in self.locks_list: lock.release() - asyncio.get_event_loop().run_until_complete(fn()) + asyncio.get_event_loop().create_task(fn()) def remove_handler(self, handler, group: int): async def fn(): @@ -146,7 +146,7 @@ class Dispatcher: for lock in self.locks_list: lock.release() - asyncio.get_event_loop().run_until_complete(fn()) + asyncio.get_event_loop().create_task(fn()) async def update_worker(self, lock): while True: From 656aa4a7cacf3fa55393aea8daf2f393b801ed17 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 17:33:33 +0200 Subject: [PATCH 122/155] Enable scheduling of more than 1 updates worker --- pyrogram/client/client.py | 24 +++++++++++++++--------- pyrogram/client/ext/base_client.py | 3 +-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 99ba1805..e7da9117 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -17,9 +17,7 @@ # along with Pyrogram. If not, see . import asyncio -import base64 import inspect -import json import logging import math import mimetypes @@ -325,7 +323,12 @@ class Client(Methods, BaseClient): await self.session.stop() raise e - self.updates_worker_task = asyncio.ensure_future(self.updates_worker()) + for _ in range(Client.UPDATES_WORKERS): + self.updates_worker_tasks.append( + asyncio.ensure_future(self.updates_worker()) + ) + + log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( @@ -367,8 +370,15 @@ class Client(Methods, BaseClient): log.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) - self.updates_queue.put_nowait(None) - await self.updates_worker_task + for _ in range(Client.UPDATES_WORKERS): + self.updates_queue.put_nowait(None) + + for task in self.updates_worker_tasks: + await task + + self.updates_worker_tasks.clear() + + log.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for media_session in self.media_sessions.values(): await media_session.stop() @@ -862,8 +872,6 @@ class Client(Methods, BaseClient): done.set() async def updates_worker(self): - log.info("UpdatesWorkerTask started") - while True: updates = await self.updates_queue.get() @@ -946,8 +954,6 @@ class Client(Methods, BaseClient): except Exception as e: log.error(e, exc_info=True) - log.info("UpdatesWorkerTask stopped") - async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 0b1f9bff..3d513031 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -24,7 +24,6 @@ import sys from pathlib import Path from pyrogram import __version__ - from ..style import Markdown, HTML from ...session.internals import MsgId @@ -105,7 +104,7 @@ class BaseClient: self.takeout_id = None self.updates_queue = asyncio.Queue() - self.updates_worker_task = None + self.updates_worker_tasks = [] self.download_queue = asyncio.Queue() self.download_worker_tasks = [] From ee1f6e2c9f421d0fddc1e88a5d12d9120d444456 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 4 Jul 2019 12:57:07 +0200 Subject: [PATCH 123/155] Fix errors and warnings when using Pyrogram async with Python <3.5.3 --- pyrogram/client/client.py | 5 ++++- pyrogram/client/methods/chats/archive_chats.py | 18 +++++++++++------- .../client/methods/chats/iter_chat_members.py | 4 ++-- pyrogram/client/methods/chats/iter_dialogs.py | 4 ++-- .../client/methods/chats/unarchive_chats.py | 18 +++++++++++------- .../client/methods/messages/iter_history.py | 4 ++-- .../methods/users/iter_profile_photos.py | 4 ++-- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index e7da9117..faac9051 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -447,7 +447,8 @@ class Client(Methods, BaseClient): Raises: RPCError: In case of a Telegram RPC error. """ - run = asyncio.get_event_loop().run_until_complete + loop = asyncio.get_event_loop() + run = loop.run_until_complete run(self.start()) @@ -460,6 +461,8 @@ class Client(Methods, BaseClient): if self.is_started: run(self.stop()) + loop.close() + return coroutine def add_handler(self, handler: Handler, group: int = 0): diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 3f53b25e..379860d5 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -19,7 +19,6 @@ from typing import Union, List from pyrogram.api import functions, types - from ...ext import BaseClient @@ -45,14 +44,19 @@ class ArchiveChats(BaseClient): if not isinstance(chat_ids, list): chat_ids = [chat_ids] + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=1 + ) + ) + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=await self.resolve_peer(chat), - folder_id=1 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index c4db1521..bdd613fd 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from string import ascii_lowercase -from typing import Union, AsyncGenerator, Optional +from typing import Union, Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -47,7 +47,7 @@ class IterChatMembers(BaseClient): limit: int = 0, query: str = "", filter: str = Filters.ALL - ) -> Optional[AsyncGenerator["pyrogram.ChatMember", None]]: + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: """Iterate through the members of a chat sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_chat_members` in a loop, thus saving you diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index bbad9b35..3162d31d 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import AsyncGenerator, Optional +from typing import Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -30,7 +30,7 @@ class IterDialogs(BaseClient): self, limit: int = 0, offset_date: int = None - ) -> Optional[AsyncGenerator["pyrogram.Dialog", None]]: + ) -> Optional[Generator["pyrogram.Dialog", None, None]]: """Iterate through a user's dialogs sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_dialogs` in a loop, thus saving diff --git a/pyrogram/client/methods/chats/unarchive_chats.py b/pyrogram/client/methods/chats/unarchive_chats.py index 56768dba..8c47557e 100644 --- a/pyrogram/client/methods/chats/unarchive_chats.py +++ b/pyrogram/client/methods/chats/unarchive_chats.py @@ -19,7 +19,6 @@ from typing import Union, List from pyrogram.api import functions, types - from ...ext import BaseClient @@ -45,14 +44,19 @@ class UnarchiveChats(BaseClient): if not isinstance(chat_ids, list): chat_ids = [chat_ids] + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=0 + ) + ) + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=await self.resolve_peer(chat), - folder_id=0 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index 2dca96fe..6d69273b 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Optional, AsyncGenerator +from typing import Union, Optional, Generator import pyrogram from async_generator import async_generator, yield_ @@ -34,7 +34,7 @@ class IterHistory(BaseClient): offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ) -> Optional[AsyncGenerator["pyrogram.Message", None]]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat history sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_history` in a loop, thus saving diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index bfe3e7f0..4e703e07 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, AsyncGenerator, Optional +from typing import Union, Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -31,7 +31,7 @@ class IterProfilePhotos(BaseClient): chat_id: Union[int, str], offset: int = 0, limit: int = 0, - ) -> Optional[AsyncGenerator["pyrogram.Message", None]]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat or a user profile photos sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_profile_photos` in a loop, thus From d5f31a8473e788de115b428569b3204ebf0baddd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 04:20:23 +0200 Subject: [PATCH 124/155] Update asyncio-dev version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index f98d0ad5..7b4ece92 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.1-asyncio" +__version__ = "0.16.0.asyncio-dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " From 9940dd678fd4f982e066488eb5595871e79b309c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 00:51:32 +0200 Subject: [PATCH 125/155] Replace ensure_future usages to create_task --- pyrogram/client/client.py | 8 ++++---- pyrogram/client/ext/dispatcher.py | 2 +- pyrogram/client/ext/syncer.py | 2 +- pyrogram/session/session.py | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2376aa36..35902611 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -325,14 +325,14 @@ class Client(Methods, BaseClient): for _ in range(Client.UPDATES_WORKERS): self.updates_worker_tasks.append( - asyncio.ensure_future(self.updates_worker()) + asyncio.create_task(self.updates_worker()) ) log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( - asyncio.ensure_future(self.download_worker()) + asyncio.create_task(self.download_worker()) ) log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) @@ -1397,7 +1397,7 @@ class Client(Methods, BaseClient): return try: - await asyncio.ensure_future(session.send(data)) + await asyncio.create_task(session.send(data)) except Exception as e: log.error(e) @@ -1418,7 +1418,7 @@ class Client(Methods, BaseClient): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)] - workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] + workers = [asyncio.create_task(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 70bee246..98d9b865 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -98,7 +98,7 @@ class Dispatcher: self.locks_list.append(asyncio.Lock()) self.update_worker_tasks.append( - asyncio.ensure_future(self.update_worker(self.locks_list[-1])) + asyncio.create_task(self.update_worker(self.locks_list[-1])) ) log.info("Started {} UpdateWorkerTasks".format(self.workers)) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 8b48e6e2..bf54f57b 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -59,7 +59,7 @@ class Syncer: @classmethod def start(cls): cls.event.clear() - asyncio.ensure_future(cls.worker()) + asyncio.create_task(cls.worker()) @classmethod def stop(cls): diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ff65483c..429de199 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -123,8 +123,8 @@ class Session: try: await self.connection.connect() - self.net_worker_task = asyncio.ensure_future(self.net_worker()) - self.recv_task = asyncio.ensure_future(self.recv()) + self.net_worker_task = asyncio.create_task(self.net_worker()) + self.recv_task = asyncio.create_task(self.recv()) self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt( @@ -137,7 +137,7 @@ class Session: self.current_salt = \ (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).salts[0] - self.next_salt_task = asyncio.ensure_future(self.next_salt()) + self.next_salt_task = asyncio.create_task(self.next_salt()) if not self.is_cdn: await self._send( @@ -157,7 +157,7 @@ class Session: timeout=self.START_TIMEOUT ) - self.ping_task = asyncio.ensure_future(self.ping()) + self.ping_task = asyncio.create_task(self.ping()) log.info("Session initialized: Layer {}".format(layer)) log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) @@ -351,7 +351,7 @@ class Session: log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): - asyncio.ensure_future(self.restart()) + asyncio.create_task(self.restart()) break From 4d324abbb5d11040028ef6aa62572b52e1e39a38 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 00:54:35 +0200 Subject: [PATCH 126/155] Don't automatically install uvloop. Let people do that People are reporting uvloop would crash with weird core-dumped errors when using other asyncio libs, such as aiohttp. Plus, this was a bad idea and people should install uvloop themselves before running their codes. --- pyrogram/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7b4ece92..407bbcff 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -28,13 +28,6 @@ __version__ = "0.16.0.asyncio-dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " -try: - import uvloop -except ImportError: - pass -else: - uvloop.install() - from .errors import RPCError from .client import * from .client.handlers import * From c30e8f9c551ce89efbef1e23cf192467ac8afdce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 01:26:29 +0200 Subject: [PATCH 127/155] Don't start the client in case run() is called with a coroutine as arg --- pyrogram/client/client.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 35902611..c1065a42 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import asyncio -import inspect import logging import math import mimetypes @@ -452,21 +451,17 @@ class Client(Methods, BaseClient): loop = asyncio.get_event_loop() run = loop.run_until_complete - run(self.start()) + if coroutine is not None: + run(coroutine) + else: + run(self.start()) + run(self.idle()) - run( - coroutine if inspect.iscoroutine(coroutine) - else coroutine() if coroutine - else self.idle() - ) - - if self.is_started: - run(self.stop()) + # TODO: Uncomment this once idle() gets refactored + # run(self.stop()) loop.close() - return coroutine - def add_handler(self, handler: Handler, group: int = 0): """Register an update handler. From 8700e3a0f3a2e4e12c9c5dfaa5382e765e1149ba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 31 Jul 2019 13:33:04 +0200 Subject: [PATCH 128/155] Fix some methods not being defined using async --- pyrogram/client/ext/base_client.py | 42 +++++++++---------- .../bots_and_keyboards/callback_query.py | 26 ++++++------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 640e7e65..230c681f 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -146,29 +146,29 @@ class BaseClient: async def answer_inline_query(self, *args, **kwargs): pass + async def get_profile_photos(self, *args, **kwargs): + pass + + async def edit_message_text(self, *args, **kwargs): + pass + + async def edit_inline_text(self, *args, **kwargs): + pass + + async def edit_message_media(self, *args, **kwargs): + pass + + async def edit_inline_media(self, *args, **kwargs): + pass + + async def edit_message_reply_markup(self, *args, **kwargs): + pass + + async def edit_inline_reply_markup(self, *args, **kwargs): + pass + def guess_mime_type(self, *args, **kwargs): pass def guess_extension(self, *args, **kwargs): pass - - def get_profile_photos(self, *args, **kwargs): - pass - - def edit_message_text(self, *args, **kwargs): - pass - - def edit_inline_text(self, *args, **kwargs): - pass - - def edit_message_media(self, *args, **kwargs): - pass - - def edit_inline_media(self, *args, **kwargs): - pass - - def edit_message_reply_markup(self, *args, **kwargs): - pass - - def edit_inline_reply_markup(self, *args, **kwargs): - pass diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 0264a67c..27cebd7e 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -129,7 +129,7 @@ class CallbackQuery(Object, Update): client=client ) - def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): + async def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): """Bound method *answer* of :obj:`CallbackQuery`. Use this method as a shortcut for: @@ -165,7 +165,7 @@ class CallbackQuery(Object, Update): The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. """ - return self._client.answer_callback_query( + return await self._client.answer_callback_query( callback_query_id=self.id, text=text, show_alert=show_alert, @@ -173,7 +173,7 @@ class CallbackQuery(Object, Update): cache_time=cache_time ) - def edit_message_text( + async def edit_message_text( self, text: str, parse_mode: Union[str, None] = object, @@ -209,7 +209,7 @@ class CallbackQuery(Object, Update): RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_text( + return await self._client.edit_message_text( chat_id=self.message.chat.id, message_id=self.message.message_id, text=text, @@ -218,7 +218,7 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) else: - return self._client.edit_inline_text( + return await self._client.edit_inline_text( inline_message_id=self.inline_message_id, text=text, parse_mode=parse_mode, @@ -226,7 +226,7 @@ class CallbackQuery(Object, Update): reply_markup=reply_markup ) - def edit_message_caption( + async def edit_message_caption( self, caption: str, parse_mode: Union[str, None] = object, @@ -257,9 +257,9 @@ class CallbackQuery(Object, Update): Raises: RPCError: In case of a Telegram RPC error. """ - return self.edit_message_text(caption, parse_mode, reply_markup) + return await self.edit_message_text(caption, parse_mode, reply_markup) - def edit_message_media( + async def edit_message_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None @@ -283,20 +283,20 @@ class CallbackQuery(Object, Update): RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_media( + return await self._client.edit_message_media( chat_id=self.message.chat.id, message_id=self.message.message_id, media=media, reply_markup=reply_markup ) else: - return self._client.edit_inline_media( + return await self._client.edit_inline_media( inline_message_id=self.inline_message_id, media=media, reply_markup=reply_markup ) - def edit_message_reply_markup( + async def edit_message_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: @@ -316,13 +316,13 @@ class CallbackQuery(Object, Update): RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_reply_markup( + return await self._client.edit_message_reply_markup( chat_id=self.message.chat.id, message_id=self.message.message_id, reply_markup=reply_markup ) else: - return self._client.edit_inline_reply_markup( + return await self._client.edit_inline_reply_markup( inline_message_id=self.inline_message_id, reply_markup=reply_markup ) From eddff4769cd2098bb51f82e674e78a9050b535c9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 10:43:09 +0200 Subject: [PATCH 129/155] Add missing async/await --- pyrogram/client/parser/parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 371c4791..21d09b42 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -30,7 +30,7 @@ class Parser: self.html = HTML(client) self.markdown = Markdown(client) - def parse(self, text: str, mode: Union[str, None] = object): + async def parse(self, text: str, mode: Union[str, None] = object): text = str(text).strip() if mode == object: @@ -48,13 +48,13 @@ class Parser: mode = mode.lower() if mode == "combined": - return self.markdown.parse(text) + return await self.markdown.parse(text) if mode in ["markdown", "md"]: - return self.markdown.parse(text, True) + return await self.markdown.parse(text, True) if mode == "html": - return self.html.parse(text) + return await self.html.parse(text) raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), From 73e8b8c66e10ce4ab6bd1d75c62e935ae83d33ba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 20:18:17 +0200 Subject: [PATCH 130/155] Update read_history.py --- pyrogram/client/methods/messages/read_history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index f5dc8630..7fa99dc9 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class ReadHistory(BaseClient): - def read_history( + async def read_history( self, chat_id: Union[int, str], max_id: int = 0 @@ -53,7 +53,7 @@ class ReadHistory(BaseClient): app.read_history("pyrogramlounge", 123456) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): q = functions.channels.ReadHistory( @@ -66,6 +66,6 @@ class ReadHistory(BaseClient): max_id=max_id ) - self.send(q) + await self.send(q) return True From 94603f1ff255f3b44158a4f1b4acb2dad8ac5542 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 10:36:57 +0200 Subject: [PATCH 131/155] Replace create_task with ensure_future for compatibility --- pyrogram/client/ext/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 98d9b865..855fefe7 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -130,7 +130,7 @@ class Dispatcher: for lock in self.locks_list: lock.release() - asyncio.get_event_loop().create_task(fn()) + asyncio.ensure_future(fn()) def remove_handler(self, handler, group: int): async def fn(): @@ -146,7 +146,7 @@ class Dispatcher: for lock in self.locks_list: lock.release() - asyncio.get_event_loop().create_task(fn()) + asyncio.ensure_future(fn()) async def update_worker(self, lock): while True: From adda199c779ccad60eb91f7cc07df57071939954 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 10:37:48 +0200 Subject: [PATCH 132/155] Revert "Replace ensure_future usages to create_task" This reverts commit 9940dd67 --- pyrogram/client/client.py | 8 ++++---- pyrogram/client/ext/dispatcher.py | 2 +- pyrogram/client/ext/syncer.py | 2 +- pyrogram/session/session.py | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8b71312d..55f20743 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -352,14 +352,14 @@ class Client(Methods, BaseClient): for _ in range(Client.UPDATES_WORKERS): self.updates_worker_tasks.append( - asyncio.create_task(self.updates_worker()) + asyncio.ensure_future(self.updates_worker()) ) log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( - asyncio.create_task(self.download_worker()) + asyncio.ensure_future(self.download_worker()) ) log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) @@ -1623,7 +1623,7 @@ class Client(Methods, BaseClient): return try: - await asyncio.create_task(session.send(data)) + await asyncio.ensure_future(session.send(data)) except Exception as e: log.error(e) @@ -1644,7 +1644,7 @@ class Client(Methods, BaseClient): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)] - workers = [asyncio.create_task(worker(session)) for session in pool for _ in range(workers_count)] + workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 855fefe7..d4388ddd 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -98,7 +98,7 @@ class Dispatcher: self.locks_list.append(asyncio.Lock()) self.update_worker_tasks.append( - asyncio.create_task(self.update_worker(self.locks_list[-1])) + asyncio.ensure_future(self.update_worker(self.locks_list[-1])) ) log.info("Started {} UpdateWorkerTasks".format(self.workers)) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index bf54f57b..8b48e6e2 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -59,7 +59,7 @@ class Syncer: @classmethod def start(cls): cls.event.clear() - asyncio.create_task(cls.worker()) + asyncio.ensure_future(cls.worker()) @classmethod def stop(cls): diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b1377064..e4699f7d 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -123,8 +123,8 @@ class Session: try: await self.connection.connect() - self.net_worker_task = asyncio.create_task(self.net_worker()) - self.recv_task = asyncio.create_task(self.recv()) + self.net_worker_task = asyncio.ensure_future(self.net_worker()) + self.recv_task = asyncio.ensure_future(self.recv()) self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt( @@ -137,7 +137,7 @@ class Session: self.current_salt = \ (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).salts[0] - self.next_salt_task = asyncio.create_task(self.next_salt()) + self.next_salt_task = asyncio.ensure_future(self.next_salt()) if not self.is_cdn: await self._send( @@ -157,7 +157,7 @@ class Session: timeout=self.START_TIMEOUT ) - self.ping_task = asyncio.create_task(self.ping()) + self.ping_task = asyncio.ensure_future(self.ping()) log.info("Session initialized: Layer {}".format(layer)) log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) @@ -351,7 +351,7 @@ class Session: log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): - asyncio.create_task(self.restart()) + asyncio.ensure_future(self.restart()) break From 5cfc412af22c72867cf7593adbb7d225566aec96 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 7 Aug 2019 14:08:06 +0200 Subject: [PATCH 133/155] Add missing await --- pyrogram/client/methods/messages/edit_inline_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 24951832..3e2ab5d2 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -76,6 +76,6 @@ class EditInlineText(BaseClient): id=utils.unpack_inline_message_id(inline_message_id), no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(text, parse_mode) + **await self.parser.parse(text, parse_mode) ) ) From 2031df15fe061067564fcb6b88199351a0db1134 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Aug 2019 11:52:12 +0200 Subject: [PATCH 134/155] Update inline_query_result_photo.py --- pyrogram/client/types/inline_mode/inline_query_result_photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index ffcc21c0..df06a2a9 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -121,7 +121,7 @@ class InlineQueryResultPhoto(InlineQueryResult): if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) From fe6c5e542d8c0dcc65913bf63758df506475532b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Aug 2019 12:25:09 +0200 Subject: [PATCH 135/155] Add missing async and await keywords --- .../client/types/inline_mode/inline_query_result_photo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index df06a2a9..35b8a874 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -91,7 +91,7 @@ class InlineQueryResultPhoto(InlineQueryResult): self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): photo = types.InputWebDocument( url=self.photo_url, size=0, @@ -117,7 +117,7 @@ class InlineQueryResultPhoto(InlineQueryResult): thumb=thumb, content=photo, send_message=( - self.input_message_content.write(self.reply_markup) + await self.input_message_content.write(self.reply_markup) if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, From 1ade49a13a887d0f0cd76bc85dc0e789624187a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Aug 2019 22:09:36 +0200 Subject: [PATCH 136/155] Fix error on serializing None when int is expected --- pyrogram/client/methods/chats/iter_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index 8265a9df..80ce44f5 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -29,7 +29,7 @@ class IterDialogs(BaseClient): async def iter_dialogs( self, limit: int = 0, - offset_date: int = None + offset_date: int = 0 ) -> Optional[Generator["pyrogram.Dialog", None, None]]: """Iterate through a user's dialogs sequentially. From aa937a704d0bad9fdf2f893b2a85cc1a9a40d9b4 Mon Sep 17 00:00:00 2001 From: YoilyL Date: Mon, 9 Sep 2019 16:56:57 +0300 Subject: [PATCH 137/155] fixed memory leak when session.send coroutine is cancelled (#311) added that when session.send coroutine is cancelled (or if any other exception is raised) the result should still be removed from the results list --- pyrogram/session/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b5f69081..3dbedef4 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -382,8 +382,8 @@ class Session: await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - - result = self.results.pop(msg_id).value + finally: + result = self.results.pop(msg_id).value if result is None: raise TimeoutError From bc7d29237d40dba92c3cbd1254b3d2561b313814 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 25 Sep 2019 18:41:06 +0200 Subject: [PATCH 138/155] Small style fix --- pyrogram/client/client.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index cb1abcaa..a0bf5af5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1881,19 +1881,23 @@ class Client(Methods, BaseClient): for session in pool: await session.stop() - async def get_file(self, media_type: int, - dc_id: int, - document_id: int, - access_hash: int, - thumb_size: str, - peer_id: int, - peer_access_hash: int, volume_id: int, - local_id: int, - file_ref: str,file_size: int, - - is_big: bool, - progress: callable, - progress_args: tuple = ()) -> str: + async def get_file( + self, + media_type: int, + dc_id: int, + document_id: int, + access_hash: int, + thumb_size: str, + peer_id: int, + peer_access_hash: int, + volume_id: int, + local_id: int, + file_ref: str, + file_size: int, + is_big: bool, + progress: callable, + progress_args: tuple = () + ) -> str: async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) From 353811ebd3afb65d442a807de6e4e7a2a112f47e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 Dec 2019 21:14:15 +0100 Subject: [PATCH 139/155] Add missing await --- pyrogram/client/methods/chats/set_chat_photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index df67923e..4c912475 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -59,7 +59,7 @@ class SetChatPhoto(BaseClient): peer = await self.resolve_peer(chat_id) if os.path.exists(photo): - photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: photo = utils.get_input_media_from_file_id(photo) photo = types.InputChatPhoto(id=photo.id) From 8e9a7a33bd7113f7773669a421c70f8adcfdb61f Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Wed, 20 Nov 2019 02:51:42 -0300 Subject: [PATCH 140/155] Add missing awaits --- pyrogram/client/methods/chats/add_chat_members.py | 12 ++++++------ pyrogram/client/methods/chats/create_channel.py | 4 ++-- pyrogram/client/methods/chats/create_group.py | 6 +++--- pyrogram/client/methods/chats/create_supergroup.py | 4 ++-- pyrogram/client/methods/chats/delete_channel.py | 6 +++--- pyrogram/client/methods/chats/delete_supergroup.py | 6 +++--- .../client/methods/chats/set_chat_permissions.py | 2 +- pyrogram/client/methods/messages/send_poll.py | 6 +++--- pyrogram/client/methods/users/get_common_chats.py | 6 +++--- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py index 8dbad1a3..ace35cf8 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class AddChatMembers(BaseClient): - def add_chat_members( + async def add_chat_members( self, chat_id: Union[int, str], user_ids: Union[Union[int, str], List[Union[int, str]]], @@ -60,26 +60,26 @@ class AddChatMembers(BaseClient): # Change forward_limit (for basic groups only) app.add_chat_members(chat_id, user_id, forward_limit=25) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if not isinstance(user_ids, list): user_ids = [user_ids] if isinstance(peer, types.InputPeerChat): for user_id in user_ids: - self.send( + await self.send( functions.messages.AddChatUser( chat_id=peer.chat_id, - user_id=self.resolve_peer(user_id), + user_id=await self.resolve_peer(user_id), fwd_limit=forward_limit ) ) else: - self.send( + await self.send( functions.channels.InviteToChannel( channel=peer, users=[ - self.resolve_peer(user_id) + await self.resolve_peer(user_id) for user_id in user_ids ] ) diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py index 9dde8781..c768ccce 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/client/methods/chats/create_channel.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class CreateChannel(BaseClient): - def create_channel( + async def create_channel( self, title: str, description: str = "" @@ -44,7 +44,7 @@ class CreateChannel(BaseClient): app.create_channel("Channel Title", "Channel Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py index a9013d81..c69d8ff8 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/client/methods/chats/create_group.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class CreateGroup(BaseClient): - def create_group( + async def create_group( self, title: str, users: Union[Union[int, str], List[Union[int, str]]] @@ -55,10 +55,10 @@ class CreateGroup(BaseClient): if not isinstance(users, list): users = [users] - r = self.send( + r = await self.send( functions.messages.CreateChat( title=title, - users=[self.resolve_peer(u) for u in users] + users=[await self.resolve_peer(u) for u in users] ) ) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py index 28b3fd1b..ddeb42bf 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -22,7 +22,7 @@ from ...ext import BaseClient class CreateSupergroup(BaseClient): - def create_supergroup( + async def create_supergroup( self, title: str, description: str = "" @@ -48,7 +48,7 @@ class CreateSupergroup(BaseClient): app.create_supergroup("Supergroup Title", "Supergroup Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py index 74fbea13..b306773b 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class DeleteChannel(BaseClient): - def delete_channel(self, chat_id: Union[int, str]) -> bool: + async def delete_channel(self, chat_id: Union[int, str]) -> bool: """Delete a channel. Parameters: @@ -39,9 +39,9 @@ class DeleteChannel(BaseClient): app.delete_channel(channel_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py index a1eb198d..3c291424 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class DeleteSupergroup(BaseClient): - def delete_supergroup(self, chat_id: Union[int, str]) -> bool: + async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: """Delete a supergroup. Parameters: @@ -39,9 +39,9 @@ class DeleteSupergroup(BaseClient): app.delete_supergroup(supergroup_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/client/methods/chats/set_chat_permissions.py index 158cc269..e1494ac3 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -107,7 +107,7 @@ class SetChatPermissions(BaseClient): r = await self.send( functions.messages.EditChatDefaultBannedRights( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), banned_rights=types.ChatBannedRights( until_date=0, send_messages=send_messages, diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index a684dda9..50ba9cc1 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,7 +24,7 @@ from pyrogram.client.ext import BaseClient class SendPoll(BaseClient): - def send_poll( + async def send_poll( self, chat_id: Union[int, str], question: str, @@ -75,9 +75,9 @@ class SendPoll(BaseClient): app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaPoll( poll=types.Poll( id=0, diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/client/methods/users/get_common_chats.py index f2c9ac00..2207e09f 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/client/methods/users/get_common_chats.py @@ -24,7 +24,7 @@ from ...ext import BaseClient class GetCommonChats(BaseClient): - def get_common_chats(self, user_id: Union[int, str]) -> list: + async def get_common_chats(self, user_id: Union[int, str]) -> list: """Get the common chats you have with a user. Parameters: @@ -46,10 +46,10 @@ class GetCommonChats(BaseClient): print(common) """ - peer = self.resolve_peer(user_id) + peer = await self.resolve_peer(user_id) if isinstance(peer, types.InputPeerUser): - r = self.send( + r = await self.send( functions.messages.GetCommonChats( user_id=peer, max_id=0, From 2daa5932c6641f8c3d3fa2445d64a731d7b77fdd Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Mon, 23 Dec 2019 19:44:58 +0530 Subject: [PATCH 141/155] Add missing asyncio keywords (#319) * fix missing await * fix empty file reference * one more await, and file reference --- .../client/methods/messages/edit_message_media.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 579e3c44..2042a675 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -108,7 +108,7 @@ class EditMessageMedia(BaseClient): peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -145,7 +145,7 @@ class EditMessageMedia(BaseClient): peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "audio/mpeg", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( @@ -165,7 +165,7 @@ class EditMessageMedia(BaseClient): id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -203,7 +203,7 @@ class EditMessageMedia(BaseClient): id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -219,7 +219,7 @@ class EditMessageMedia(BaseClient): peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "application/zip", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeFilename( @@ -234,7 +234,7 @@ class EditMessageMedia(BaseClient): id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): From e316d18bf4bfcae7113588abcc5e021577a39f12 Mon Sep 17 00:00:00 2001 From: Yusuf_M_Thon_iD <32301831+Sunda001@users.noreply.github.com> Date: Sat, 1 Feb 2020 20:10:46 +0700 Subject: [PATCH 142/155] Add missing file_ref in set_chat_photo (#343) --- pyrogram/client/methods/chats/set_chat_photo.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 4c912475..689cc15f 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,7 +27,8 @@ class SetChatPhoto(BaseClient): async def set_chat_photo( self, chat_id: Union[int, str], - photo: str + photo: str, + file_ref: str = None, ) -> bool: """Set a new profile photo for the chat. @@ -40,6 +41,10 @@ class SetChatPhoto(BaseClient): photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. + + file_ref (``str``, *optional*): + A valid file reference obtained by a recently fetched media message. + To be used in combination with a file id in case a file reference is needed. Returns: ``bool``: True on success. @@ -54,14 +59,14 @@ class SetChatPhoto(BaseClient): app.set_chat_photo(chat_id, "photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id) + app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) """ peer = await self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: - photo = utils.get_input_media_from_file_id(photo) + photo = utils.get_input_media_from_file_id(photo, file_ref, 2) photo = types.InputChatPhoto(id=photo.id) if isinstance(peer, types.InputPeerChat): From df5de3e5836e77792aca22509d8f94bd041ae9b5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 4 Feb 2020 17:03:33 +0100 Subject: [PATCH 143/155] Revert "Add missing file_ref in set_chat_photo (#343)" (#366) This reverts commit e316d18bf4bfcae7113588abcc5e021577a39f12. --- pyrogram/client/methods/chats/set_chat_photo.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 689cc15f..4c912475 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,8 +27,7 @@ class SetChatPhoto(BaseClient): async def set_chat_photo( self, chat_id: Union[int, str], - photo: str, - file_ref: str = None, + photo: str ) -> bool: """Set a new profile photo for the chat. @@ -41,10 +40,6 @@ class SetChatPhoto(BaseClient): photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. - - file_ref (``str``, *optional*): - A valid file reference obtained by a recently fetched media message. - To be used in combination with a file id in case a file reference is needed. Returns: ``bool``: True on success. @@ -59,14 +54,14 @@ class SetChatPhoto(BaseClient): app.set_chat_photo(chat_id, "photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) + app.set_chat_photo(chat_id, photo.file_id) """ peer = await self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: - photo = utils.get_input_media_from_file_id(photo, file_ref, 2) + photo = utils.get_input_media_from_file_id(photo) photo = types.InputChatPhoto(id=photo.id) if isinstance(peer, types.InputPeerChat): From 3cf758433dfb5259c7460bf9e2e1caa5bc02fbaa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 18:35:50 +0200 Subject: [PATCH 144/155] Add missing await keywords --- pyrogram/client/methods/messages/send_dice.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index c5f95d4b..389b6597 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -24,7 +24,7 @@ from pyrogram.client.ext import BaseClient class SendDice(BaseClient): - def send_dice( + async def send_dice( self, chat_id: Union[int, str], disable_notification: bool = None, @@ -68,9 +68,9 @@ class SendDice(BaseClient): app.send_dice("pyrogramlounge") """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaDice(), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -82,11 +82,8 @@ class SendDice(BaseClient): ) for i in r.updates: - if isinstance( - i, - (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage) - ): - return pyrogram.Message._parse( + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, From c1a835b74ef3c5ddf79b18d764cb386687f6647c Mon Sep 17 00:00:00 2001 From: Real Phoenix <51527258+rsktg@users.noreply.github.com> Date: Mon, 6 Apr 2020 17:52:38 +0530 Subject: [PATCH 145/155] Add more Chat bound methods (#383) * Add more bound methods Bound methods for get_chat_member, get_chat_members, iter_chat_members, add_chat_members * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 4 + pyrogram/client/types/user_and_chats/chat.py | 123 ++++++++++++++++++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c78a47ee..a707af07 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -450,6 +450,10 @@ def pyrogram_api(): Chat.unban_member Chat.restrict_member Chat.promote_member + Chat.get_member + Chat.get_members + Chat.iter_members + Chat.add_members Chat.join Chat.leave """, diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 46ff5662..914159e3 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Generator, Optional import pyrogram from pyrogram.api import types @@ -725,3 +725,124 @@ class Chat(Object): """ return await self._client.export_chat_invite_link(self.id) + + async def get_member( + self, + user_id: Union[int, str], + ) -> "pyrogram.ChatMember": + """Bound method *get_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.get_member(user_id) + + Returns: + :obj:`ChatMember`: On success, a chat member is returned. + """ + + return await self._client.get_chat_member( + self.id, + user_id=user_id + ) + + async def get_members( + self, + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = Filters.ALL + ) -> List["pyrogram.ChatMember"]: + """Bound method *get_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_members(chat_id) + + Example: + .. code-block:: python + # Get first 200 recent members + chat.get_members() + + Returns: + List of :obj:`ChatMember`: On success, a list of chat members is returned. + """ + + return await self._client.get_chat_members( + self.id, + offset=offset, + limit=limit, + query=query, + filter=filter + ) + + async def iter_members( + self, + limit: int = 0, + query: str = "", + filter: str = Filters.ALL + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: + """Bound method *iter_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + for member in client.iter_chat_members(chat_id): + print(member.user.first_name) + + Example: + .. code-block:: python + + for member in chat.iter_members(): + print(member.user.first_name) + + Returns: + ``Generator``: A generator yielding :obj:`ChatMember` objects. + """ + + return self._client.iter_chat_members( + self.id, + limit=limit, + query=query, + filter=filter + ) + + async def add_members( + self, + user_ids: Union[Union[int, str], List[Union[int, str]]], + forward_limit: int = 100 + ) -> bool: + """Bound method *add_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.add_chat_members(chat_id, user_id) + + Example: + .. code-block:: python + + chat.add_members(user_id) + + Returns: + ``bool``: On success, True is returned. + """ + + return await self._client.add_chat_members( + self.id, + user_ids=user_ids, + forward_limit=forward_limit + ) \ No newline at end of file From 1b0b467d7b9df75b03629f751f8ed0021b6e9fda Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Apr 2020 16:05:21 +0200 Subject: [PATCH 146/155] Fix iter_members not working properly as async generator --- pyrogram/client/types/user_and_chats/chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index f1369a00..4b3c1f9d 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -781,7 +781,7 @@ class Chat(Object): filter=filter ) - async def iter_members( + def iter_members( self, limit: int = 0, query: str = "", @@ -806,7 +806,7 @@ class Chat(Object): ``Generator``: A generator yielding :obj:`ChatMember` objects. """ - return await self._client.iter_chat_members( + return self._client.iter_chat_members( self.id, limit=limit, query=query, From 23789393fe22dc45f1847f898a4b5390d7cf206c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 May 2020 16:37:03 +0200 Subject: [PATCH 147/155] Fix missing async/await for set_slow_mode --- pyrogram/client/methods/chats/set_slow_mode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index cf6c7096..445abd4b 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -23,7 +23,7 @@ from ...ext import BaseClient class SetSlowMode(BaseClient): - def set_slow_mode( + async def set_slow_mode( self, chat_id: Union[int, str], seconds: int, @@ -47,9 +47,9 @@ class SetSlowMode(BaseClient): app.set_slow_mode("pyrogramchat", 60) """ - self.send( + await self.send( functions.channels.ToggleSlowMode( - channel=self.resolve_peer(chat_id), + channel=await self.resolve_peer(chat_id), seconds=seconds ) ) From dd9423bbb1825770385d69e4a25cb9bae18b075f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:02:06 +0200 Subject: [PATCH 148/155] Update Pyrogram to v0.17.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 13ea5ce5..301e4e64 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.17.0" +__version__ = "0.17.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 48e45fee9b3018fcd509362ea5775ed568379431 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:16:52 +0200 Subject: [PATCH 149/155] Add missing update_profile to docs and Client --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/users/__init__.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index ff205f9b..6b596b2f 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -226,6 +226,7 @@ def pyrogram_api(): set_profile_photo delete_profile_photos update_username + update_profile block_user unblock_user """, diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 1980303f..65171af6 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -26,6 +26,7 @@ from .get_users import GetUsers from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto from .unblock_user import UnblockUser +from .update_profile import UpdateProfile from .update_username import UpdateUsername @@ -40,6 +41,7 @@ class Users( UpdateUsername, GetProfilePhotosCount, IterProfilePhotos, - UnblockUser + UnblockUser, + UpdateProfile ): pass From 3502153b709695a56eae2e7517237099c6925271 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:17:05 +0200 Subject: [PATCH 150/155] Add copy button prompt text to ignore --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0302328d..a86895ce 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,6 +52,8 @@ napoleon_use_rtype = False pygments_style = "friendly" +copybutton_prompt_text = "$ " + html_title = "Pyrogram Documentation" html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] From e4028fa6a7ec69ac3e372519ef70856c548cf448 Mon Sep 17 00:00:00 2001 From: Cezar H <29507335+usernein@users.noreply.github.com> Date: Thu, 14 May 2020 06:56:58 -0300 Subject: [PATCH 151/155] Add missing await (#403) await client.send_poll(...) was returning a coroutine instead of the Message object --- pyrogram/client/methods/messages/send_poll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 0248d0b2..7607a546 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -123,7 +123,7 @@ class SendPoll(BaseClient): for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, From f4d075597f1e5169debe81bd99434bc99eacea92 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 May 2020 22:19:15 +0200 Subject: [PATCH 152/155] Add missing async/await --- pyrogram/client/methods/users/update_profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index 91a7950c..a968128d 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -21,7 +21,7 @@ from ...ext import BaseClient class UpdateProfile(BaseClient): - def update_profile( + async def update_profile( self, first_name: str = None, last_name: str = None, @@ -60,7 +60,7 @@ class UpdateProfile(BaseClient): """ return bool( - self.send( + await self.send( functions.account.UpdateProfile( first_name=first_name, last_name=last_name, From dd9b55f256c306bb26f7985e0cab07dd2be278a0 Mon Sep 17 00:00:00 2001 From: Ripe <42308266+Ripeey@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:27:29 +0000 Subject: [PATCH 153/155] Update inline_query_result_animation.py (#435) add missing await --- .../client/types/inline_mode/inline_query_result_animation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py index 756ee91a..d53bbd59 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -91,7 +91,7 @@ class InlineQueryResultAnimation(InlineQueryResult): self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): animation = types.InputWebDocument( url=self.animation_url, size=0, @@ -121,7 +121,7 @@ class InlineQueryResultAnimation(InlineQueryResult): if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) From 589be971667267aaa59680912a2279dba9b27d45 Mon Sep 17 00:00:00 2001 From: Mendel E <31070530+mendelmaleh@users.noreply.github.com> Date: Mon, 20 Jul 2020 22:04:24 -0400 Subject: [PATCH 154/155] Add parse_mode property to Client (#443) * Add parse_mode property to Client This breaks set_parse_mode * Add back set_parse_mode for backwards compatibility --- pyrogram/client/client.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 3f60b2b0..c0fbeb54 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -143,6 +143,11 @@ class Client(Methods, BaseClient): Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. This is an alternative way to setup plugins if you don't want to use the *config.ini* file. + parse_mode (``str``, *optional*): + The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. When updates are disabled your client can't receive any new message. @@ -184,6 +189,7 @@ class Client(Methods, BaseClient): workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, + parse_mode: str = BaseClient.PARSE_MODES[0], no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD @@ -210,6 +216,7 @@ class Client(Methods, BaseClient): self.workdir = Path(workdir) self.config_file = Path(config_file) self.plugins = plugins + self.parse_mode = parse_mode self.no_updates = no_updates self.takeout = takeout self.sleep_threshold = sleep_threshold @@ -1145,6 +1152,21 @@ class Client(Methods, BaseClient): """ return self.storage.export_session_string() + @property + def parse_mode(self): + return self._parse_mode + + @parse_mode.setter + def parse_mode(self, parse_mode: Union[str, None] = "combined"): + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self._parse_mode = parse_mode + + # TODO: redundant, remove in next major version def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): """Set the parse mode to be used globally by the client. @@ -1190,12 +1212,6 @@ class Client(Methods, BaseClient): app.send_message("haskell", "5. **markdown** and html") """ - if parse_mode not in self.PARSE_MODES: - raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), - parse_mode - )) - self.parse_mode = parse_mode def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) -> bool: From ecab62ce84f9f2cb3eadd5d6664cf5b99c9ab66d Mon Sep 17 00:00:00 2001 From: Hasibul Kobir <46620128+HasibulKabir@users.noreply.github.com> Date: Fri, 21 Aug 2020 11:33:24 +0600 Subject: [PATCH 155/155] Add support for both sync and async filters (#437) * support for both sync and async filters * Add whitespace for readability * moving to handler.check for coroutine function Ref: https://github.com/pyrogram/pyrogram/pull/437#discussion_r451626488 * add last line Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/ext/dispatcher.py | 3 +- pyrogram/client/filters/filter.py | 40 ++++++++++++++++--- .../handlers/deleted_messages_handler.py | 4 +- pyrogram/client/handlers/handler.py | 20 +++++++--- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 818bc60c..d8308944 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -188,8 +188,9 @@ class Dispatcher: if isinstance(handler, handler_type): try: - if handler.check(parsed_update): + if (await handler.check(parsed_update)): args = (parsed_update,) + except Exception as e: log.error(e, exc_info=True) continue diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py index eb89b3c3..67067e03 100644 --- a/pyrogram/client/filters/filter.py +++ b/pyrogram/client/filters/filter.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio + + class Filter: def __call__(self, message): raise NotImplementedError @@ -34,8 +37,13 @@ class InvertFilter(Filter): def __init__(self, base): self.base = base - def __call__(self, message): - return not self.base(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + return not x class AndFilter(Filter): @@ -43,8 +51,18 @@ class AndFilter(Filter): self.base = base self.other = other - def __call__(self, message): - return self.base(message) and self.other(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + if asyncio.iscoroutinefunction(self.other.__call__): + y = await self.other(message) + else: + y = self.other(message) + + return x and y class OrFilter(Filter): @@ -52,5 +70,15 @@ class OrFilter(Filter): self.base = base self.other = other - def __call__(self, message): - return self.base(message) or self.other(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + if asyncio.iscoroutinefunction(self.other.__call__): + y = await self.other(message) + else: + y = self.other(message) + + return x or y diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index 7312ba90..03863541 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -46,5 +46,5 @@ class DeletedMessagesHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - def check(self, messages): - return super().check(messages[0]) + async def check(self, messages): + return (await super().check(messages[0])) diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 0eb132d1..d50b069b 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -16,14 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio + + class Handler: def __init__(self, callback: callable, filters=None): self.callback = callback self.filters = filters - def check(self, update): - return ( - self.filters(update) - if callable(self.filters) - else True - ) + async def check(self, update): + + if callable(self.filters): + if asyncio.iscoroutinefunction(self.filters.__call__): + return (await self.filters(update)) + + else: + return self.filters(update) + + else: + return True