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 0001/1652] 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 a53295ce7ce..b03e8852823 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 @@ def close(self):
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 0002/1652] 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 5df8aacb0a7..f006cadd287 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 @@
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 @@ def __init__(self, proxy: dict):
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()
+
+ async def send(self, data: bytes):
+ self.writer.write(data)
+ await self.writer.drain()
- def recvall(self, length: int) -> bytes or None:
+ 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 0003/1652] 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 ce662e61f4e..016e91e4d39 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 0004/1652] 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 b03e8852823..cab31b62d9e 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -40,7 +40,7 @@ def __init__(self, address: tuple, proxy: dict, mode: int = 2):
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 0005/1652] 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 cab31b62d9e..b4705fca46c 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -40,17 +40,17 @@ def __init__(self, address: tuple, proxy: dict, mode: int = 2):
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 @@ async def connect(self):
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 0006/1652] 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 4b2e25961d1..82c7b6052d7 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 0007/1652] 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 7e90cfff5c0..173e8846100 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 @@
class Result:
def __init__(self):
self.value = None
- self.event = Event()
+ self.event = asyncio.Event()
class Session:
@@ -115,47 +113,38 @@ def __init__(self,
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 @@ def start(self):
)
)
- 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 @@ def start(self):
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 @@ def unpack(self, b: BytesIO) -> Message:
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 @@ def net_worker(self):
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 @@ def net_worker(self):
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 @@ def ping(self):
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 @@ def next_salt(self):
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 @@ def _send(self, data: Object, wait_response: bool = True):
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 @@ def _send(self, data: Object, wait_response: bool = True):
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 @@ def send(self, data: Object, retries: int = MAX_RETRIES):
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 0008/1652] First step of Client conversion using asyncio
---
pyrogram/client/client.py | 107 +++++++++++++++++++-------------------
1 file changed, 53 insertions(+), 54 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 73f459660ed..8eba760a1de 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -34,7 +34,6 @@
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 @@ def proxy(self, value):
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 @@ def start(self, debug: bool = False):
client=self
)
- self.session.start()
+ await self.session.start()
self.is_started = True
if self.user_id is None:
@@ -224,66 +223,66 @@ def start(self, debug: bool = False):
self.send(functions.messages.GetPinnedDialogs())
self.get_dialogs_chunk(0)
else:
- 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()
+ 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()
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 @@ def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
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 @@ def send(self, data: Object):
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 0009/1652] 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 b4705fca46c..a9aba7c142d 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 0010/1652] 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 173e8846100..1cd27c5e6fb 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -324,7 +324,7 @@ async def ping(self):
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 0011/1652] 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 1cd27c5e6fb..20d3c81aeda 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -207,9 +207,9 @@ async def stop(self):
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 0012/1652] 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 08ed44f0082..3112729d649 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 00000000000..d42caf7cf23
--- /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 0013/1652] 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 20d3c81aeda..7b7942f9744 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 @@ async def start(self):
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 @@ 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 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 @@ async def net_worker(self):
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 @@ async def _send(self, data: Object, wait_response: bool = True):
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 0014/1652] 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 7b7942f9744..ece4b6625c9 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -205,47 +205,8 @@ async def stop(self):
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 0015/1652] 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 ece4b6625c9..ba224f46ae9 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -259,7 +259,7 @@ async def net_worker(self):
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 0016/1652] 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 ba224f46ae9..ba0013cc01d 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -209,8 +209,7 @@ async def restart(self):
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 @@ async def net_worker(self):
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 @@ async def ping(self):
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 @@ async def next_salt(self):
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 @@ async def recv(self):
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 0017/1652] 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 ba0013cc01d..30d4412d2b0 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -286,8 +286,7 @@ async def ping(self):
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 @@ async def next_salt(self):
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 0018/1652] 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 30d4412d2b0..f16081dfb7c 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 0019/1652] 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 f16081dfb7c..2dfa0221943 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -289,9 +289,11 @@ async def ping(self):
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 0020/1652] 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 2dfa0221943..b5def9a0a17 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -25,9 +25,9 @@
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 @@ async def net_worker(self):
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 0021/1652] 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 b5def9a0a17..993ad2fa1ec 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -204,8 +204,8 @@ async def stop(self):
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 0022/1652] 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 d42caf7cf23..10839126632 100644
--- a/pyrogram/crypto/mtproto.py
+++ b/pyrogram/crypto/mtproto.py
@@ -40,14 +40,13 @@ def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_k
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 993ad2fa1ec..c3dec02e3ba 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -219,7 +219,6 @@ async def net_worker(self):
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 0023/1652] 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 c3dec02e3ba..60276947206 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -334,7 +334,10 @@ async def recv(self):
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 0024/1652] 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 60276947206..f06264c0813 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -186,11 +186,11 @@ async def stop(self):
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 0025/1652] 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 f06264c0813..3a1fefd861c 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -167,7 +167,7 @@ async def start(self):
self.is_connected.set()
- log.debug("Session started")
+ log.info("Session started")
async def stop(self):
self.is_connected.clear()
@@ -201,7 +201,7 @@ async def stop(self):
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 0026/1652] 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 3a1fefd861c..d58eae379f1 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -308,10 +308,8 @@ async def next_salt(self):
# 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 @@ async def recv(self):
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 0027/1652] 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 51be2ebb0e8..8efb6584d9a 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.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)
- )
- )
-
- self.workers_list[-1].start()
- def stop(self):
- for _ in range(self.workers):
- self.updates.put(None)
+ self.update_worker_task = None
+ self.updates = asyncio.Queue()
+ self.groups = OrderedDict()
- for i in self.workers_list:
- i.join()
+ async def start(self):
+ self.update_worker_task = asyncio.ensure_future(self.update_worker())
- 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 @@ def remove_handler(self, handler, group: int):
"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 @@ def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool
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 @@ def update_worker(self):
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 @@ def update_worker(self):
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 @@ def update_worker(self):
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 0028/1652] 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 a9aba7c142d..4a25b72e17f 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -25,6 +25,7 @@
class Connection:
+ TIMEOUT = 10
MAX_RETRIES = 3
MODES = {
@@ -49,8 +50,8 @@ async def connect(self):
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 0029/1652] 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 4a25b72e17f..746dbf06b52 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -64,7 +64,10 @@ def close(self):
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 0030/1652] 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 746dbf06b52..c7210ad6a96 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -57,6 +57,7 @@ async def connect(self):
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 0031/1652] 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 c7210ad6a96..3e27638b9b8 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -25,7 +25,6 @@
class Connection:
- TIMEOUT = 10
MAX_RETRIES = 3
MODES = {
@@ -50,8 +49,8 @@ async def connect(self):
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 f006cadd287..f541153eaa0 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -34,6 +34,8 @@
class TCP:
+ TIMEOUT = 10
+
def __init__(self, proxy: dict):
self.proxy = proxy
@@ -41,6 +43,7 @@ def __init__(self, proxy: dict):
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 @@ async def recv(self, length: int = 0):
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 d58eae379f1..6479dfdd079 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -332,10 +332,7 @@ async def recv(self):
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 0032/1652] 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 8efb6584d9a..a77418c17a1 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -45,16 +45,28 @@ def __init__(self, client, workers):
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 @@ async def dispatch(self, update, users: dict = None, chats: dict = None, is_raw:
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 @@ async def update_worker(self):
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 0033/1652] 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 809561875e9..a41991fbae2 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -67,14 +67,14 @@ def unpack(b: BytesIO):
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 @@ def create(self):
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 @@ def create(self):
# 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 @@ def create(self):
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 0034/1652] 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 125c5ce051d..66d28da1a85 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 @@ def add(cls, client):
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 @@ def remove(cls, client):
@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 0035/1652] 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 a77418c17a1..79480dfb524 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -119,7 +119,6 @@ async def dispatch(self, update, users: dict = None, chats: dict = None, is_raw:
async def update_worker(self):
while True:
- tasks = []
update = await self.updates.get()
if update is None:
@@ -130,13 +129,13 @@ async def update_worker(self):
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 @@ async def update_worker(self):
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 @@ async def update_worker(self):
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 0036/1652] 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 9c0fb26b9e2..04a6ac12bc1 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 @@ def __init__(self):
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 0037/1652] 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 d7a09ee1493..bb4efb4c5eb 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 0038/1652] 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 a4baa166cce..be6fbc0cea2 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 @@
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 @@ def answer_callback_query(self,
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 52c3b0051c4..86ab18b5dbb 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 @@
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 @@ def get_inline_bot_results(self,
# 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 947433cddbb..3ce58cd813e 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 @@
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 @@ def send_inline_bot_result(self,
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 dc289af3264..26febf1d2d4 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 @@
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 @@ def export_chat_invite_link(self, chat_id: int or str):
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 194e6171d6c..9b5a5fe8fc1 100644
--- a/pyrogram/client/methods/chats/get_chat.py
+++ b/pyrogram/client/methods/chats/get_chat.py
@@ -21,7 +21,7 @@
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 @@ def get_chat(self, chat_id: int or str):
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 b7b8d42c672..75f8033fa12 100644
--- a/pyrogram/client/methods/chats/join_chat.py
+++ b/pyrogram/client/methods/chats/join_chat.py
@@ -21,7 +21,7 @@
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 @@ def join_chat(self, chat_id: str):
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 @@ def join_chat(self, chat_id: str):
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 6275718c802..5b8dda53e75 100644
--- a/pyrogram/client/methods/chats/kick_chat_member.py
+++ b/pyrogram/client/methods/chats/kick_chat_member.py
@@ -21,10 +21,10 @@
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 @@ def kick_chat_member(self,
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 @@ def kick_chat_member(self,
)
)
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 55d6ef218d5..9d7dfcef366 100644
--- a/pyrogram/client/methods/chats/leave_chat.py
+++ b/pyrogram/client/methods/chats/leave_chat.py
@@ -21,7 +21,7 @@
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 @@ def leave_chat(self, chat_id: int or str, delete: bool = False):
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 @@ def leave_chat(self, chat_id: int or str, delete: bool = False):
)
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 eb70578a3df..9cfe426b8ac 100644
--- a/pyrogram/client/methods/chats/promote_chat_member.py
+++ b/pyrogram/client/methods/chats/promote_chat_member.py
@@ -21,17 +21,17 @@
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 @@ def promote_chat_member(self,
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 ae1e4d9c113..a9439ed1b51 100644
--- a/pyrogram/client/methods/chats/restrict_chat_member.py
+++ b/pyrogram/client/methods/chats/restrict_chat_member.py
@@ -21,14 +21,14 @@
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 @@ def restrict_chat_member(self,
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 b0916eb4de5..ed00f4283ae 100644
--- a/pyrogram/client/methods/chats/unban_chat_member.py
+++ b/pyrogram/client/methods/chats/unban_chat_member.py
@@ -21,9 +21,9 @@
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 @@ def unban_chat_member(self,
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 10b5e41506b..75f4f8a80fe 100644
--- a/pyrogram/client/methods/contacts/add_contacts.py
+++ b/pyrogram/client/methods/contacts/add_contacts.py
@@ -21,7 +21,7 @@
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 @@ def add_contacts(self, contacts: list):
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 ed3d67f93b4..ef133d8cd8d 100644
--- a/pyrogram/client/methods/contacts/delete_contacts.py
+++ b/pyrogram/client/methods/contacts/delete_contacts.py
@@ -22,7 +22,7 @@
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 @@ def delete_contacts(self, ids: list):
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 376e8be2a7e..b73a1f2c906 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 @@
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 @@ def get_contacts(self):
"""
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 5eb04fbc9d6..56a89472a6e 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 @@ def download_media(self,
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 4b34dd406b5..b770f60e1b4 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 @@
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 @@ def send_chat_action(self,
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 606e54b567c..03ca9487921 100644
--- a/pyrogram/client/methods/messages/forward_messages.py
+++ b/pyrogram/client/methods/messages/forward_messages.py
@@ -21,11 +21,11 @@
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 @@ def forward_messages(self,
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 @@ def forward_messages(self,
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 4089dde9240..d6c6479a326 100644
--- a/pyrogram/client/methods/messages/get_history.py
+++ b/pyrogram/client/methods/messages/get_history.py
@@ -22,12 +22,12 @@
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 @@ def get_history(self,
: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 @@ def get_history(self,
}
if reply_to_messages:
- temp = self.get_messages(
+ temp = await self.get_messages(
chat_id, reply_to_messages,
replies=0
)
@@ -93,7 +93,7 @@ def get_history(self,
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 49535a406cf..54c11830c9c 100644
--- a/pyrogram/client/methods/messages/get_messages.py
+++ b/pyrogram/client/methods/messages/get_messages.py
@@ -21,10 +21,10 @@
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 @@ def get_messages(self,
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 @@ def get_messages(self,
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 41f4457f604..00ccbe4d41b 100644
--- a/pyrogram/client/methods/messages/media/send_audio.py
+++ b/pyrogram/client/methods/messages/media/send_audio.py
@@ -27,19 +27,19 @@
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 @@ def send_audio(self,
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 @@ def send_audio(self,
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 @@ def send_audio(self,
)
)
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 eb1bb6c4508..2965fb5a5d8 100644
--- a/pyrogram/client/methods/messages/media/send_contact.py
+++ b/pyrogram/client/methods/messages/media/send_contact.py
@@ -21,14 +21,14 @@
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 @@ def send_contact(self,
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 @@ def send_contact(self,
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 1092147f7a6..f32f78c6ebe 100644
--- a/pyrogram/client/methods/messages/media/send_document.py
+++ b/pyrogram/client/methods/messages/media/send_document.py
@@ -27,16 +27,16 @@
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 @@ def send_document(self,
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 @@ def send_document(self,
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 @@ def send_document(self,
)
)
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 0d4bb4b97a0..bdda234ed98 100644
--- a/pyrogram/client/methods/messages/media/send_gif.py
+++ b/pyrogram/client/methods/messages/media/send_gif.py
@@ -27,7 +27,7 @@
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 @@ def send_gif(self,
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 @@ def send_gif(self,
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 @@ def send_gif(self,
)
)
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 08dac02b38c..0a1a1776aff 100644
--- a/pyrogram/client/methods/messages/media/send_location.py
+++ b/pyrogram/client/methods/messages/media/send_location.py
@@ -21,13 +21,13 @@
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 @@ def send_location(self,
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 @@ def send_location(self,
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 6d004d9fa9e..f0af01becd2 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 @@ def send_media_group(self,
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 @@ def send_media_group(self,
)
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 @@ def send_media_group(self,
)
)
- 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 52e98ff14b4..e066deef060 100644
--- a/pyrogram/client/methods/messages/media/send_photo.py
+++ b/pyrogram/client/methods/messages/media/send_photo.py
@@ -26,17 +26,17 @@
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 @@ def send_photo(self,
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 @@ def send_photo(self,
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 @@ def send_photo(self,
)
)
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 639e360093a..7d559d1c152 100644
--- a/pyrogram/client/methods/messages/media/send_sticker.py
+++ b/pyrogram/client/methods/messages/media/send_sticker.py
@@ -26,14 +26,14 @@
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 @@ def send_sticker(self,
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 @@ def send_sticker(self,
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 @@ def send_sticker(self,
)
)
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 d65ea43bcbd..dcb3639d898 100644
--- a/pyrogram/client/methods/messages/media/send_venue.py
+++ b/pyrogram/client/methods/messages/media/send_venue.py
@@ -21,16 +21,16 @@
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 @@ def send_venue(self,
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 @@ def send_venue(self,
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 a4cc03093f6..f7c7d66d89e 100644
--- a/pyrogram/client/methods/messages/media/send_video.py
+++ b/pyrogram/client/methods/messages/media/send_video.py
@@ -27,21 +27,21 @@
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 @@ def send_video(self,
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 @@ def send_video(self,
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 @@ def send_video(self,
)
)
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 d7b417d5802..6eb2c252696 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 @@
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 @@ def send_video_note(self,
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 @@ def send_video_note(self,
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 @@ def send_video_note(self,
)
)
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 ae21de6d639..114ee0730c4 100644
--- a/pyrogram/client/methods/messages/media/send_voice.py
+++ b/pyrogram/client/methods/messages/media/send_voice.py
@@ -27,17 +27,17 @@
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 @@ def send_voice(self,
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 @@ def send_voice(self,
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 @@ def send_voice(self,
)
)
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 44acaa2ecc2..0009d4998de 100644
--- a/pyrogram/client/methods/messages/send_message.py
+++ b/pyrogram/client/methods/messages/send_message.py
@@ -22,14 +22,14 @@
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 @@ def send_message(self,
"""
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 @@ def send_message(self,
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 3d29bf55f63..2853ce2577c 100644
--- a/pyrogram/client/methods/messages/update/delete_messages.py
+++ b/pyrogram/client/methods/messages/update/delete_messages.py
@@ -21,10 +21,10 @@
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 @@ def delete_messages(self,
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 90bf26f788e..e2bade978dc 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 @@
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 @@ def edit_message_caption(self,
"""
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 @@ def edit_message_caption(self,
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 295eb2588f0..ec7c76382c8 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 @@
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 @@ def edit_message_reply_markup(self,
: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 @@ def edit_message_reply_markup(self,
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 be7b380cfb0..6fa50e71ac8 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 @@
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 @@ def edit_message_text(self,
"""
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 @@ def edit_message_text(self,
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 0039/1652] 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 045a0cc9e1f..e066d8ba8a5 100644
--- a/pyrogram/client/methods/password/change_cloud_password.py
+++ b/pyrogram/client/methods/password/change_cloud_password.py
@@ -24,7 +24,7 @@
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 @@ def change_cloud_password(self, current_password: str, new_password: str, new_hi
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 @@ def change_cloud_password(self, current_password: str, new_password: str, new_hi
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 639879cb11f..496430ad4c4 100644
--- a/pyrogram/client/methods/password/enable_cloud_password.py
+++ b/pyrogram/client/methods/password/enable_cloud_password.py
@@ -24,7 +24,7 @@
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 @@ def enable_cloud_password(self, password: str, hint: str = "", email: str = ""):
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 bfbb2c8bf9d..5392433f7c6 100644
--- a/pyrogram/client/methods/password/remove_cloud_password.py
+++ b/pyrogram/client/methods/password/remove_cloud_password.py
@@ -23,7 +23,7 @@
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 @@ def remove_cloud_password(self, password: str):
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 80ee65e9319..f191e29863c 100644
--- a/pyrogram/client/methods/users/get_me.py
+++ b/pyrogram/client/methods/users/get_me.py
@@ -21,7 +21,7 @@
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 @@ def get_me(self):
: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 42fb84bb034..a58e9d52e39 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 @@
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 @@ def get_user_profile_photos(self,
: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 400e35a1739..33c38900ef9 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 @@ def get_users(self, user_ids):
"""
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 0040/1652] 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 74f2a0a6db7..9b712c1f78d 100644
--- a/pyrogram/client/types/message.py
+++ b/pyrogram/client/types/message.py
@@ -310,14 +310,14 @@ def __init__(
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 @@ def reply_text(self,
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 @@ def reply_text(self,
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 @@ def forward(self,
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 @@ def delete(self, revoke: bool = True):
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 0041/1652] Client becomes async
---
pyrogram/client/client.py | 278 +++++++++++++++++---------------------
1 file changed, 127 insertions(+), 151 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 8eba760a1de..a3197d0c555 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 shutil
import struct
import tempfile
-import threading
import time
from configparser import ConfigParser
from datetime import datetime
@@ -43,11 +43,11 @@
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 @@ def proxy(self, value):
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 @@ async def start(self, debug: bool = False):
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 @@ async def start(self, debug: bool = False):
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 @@ async def start(self, debug: bool = False):
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 @@ async def stop(self):
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 @@ def remove_handler(self, handler, group: int = 0):
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 @@ def authorize_bot(self):
)
)
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 @@ def authorize_bot(self):
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 @@ def authorize_user(self):
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 @@ def authorize_user(self):
)
)
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 @@ def authorize_user(self):
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 @@ def authorize_user(self):
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 @@ def authorize_user(self):
try:
if phone_registered:
- r = self.send(
+ r = await self.send(
functions.auth.SignIn(
self.phone_number,
phone_code_hash,
@@ -455,7 +436,7 @@ def authorize_user(self):
)
else:
try:
- self.send(
+ await self.send(
functions.auth.SignIn(
self.phone_number,
phone_code_hash,
@@ -468,7 +449,7 @@ def authorize_user(self):
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 @@ def authorize_user(self):
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 @@ def authorize_user(self):
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 @@ def fetch_peers(self, entities: list):
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 @@ def download_worker(self):
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 @@ def download_worker(self):
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 @@ def updates_worker(self):
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 @@ def updates_worker(self):
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 @@ def updates_worker(self):
)
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 @@ def updates_worker(self):
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 @@ def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
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 @@ def load_config(self):
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 @@ def save_session(self):
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 @@ def get_dialogs_chunk(self, offset_date):
)
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 @@ def resolve_peer(self, peer_id: int or str):
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 @@ def resolve_peer(self, peer_id: int or str):
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 @@ def resolve_peer(self, peer_id: int or str):
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 @@ def save_file(self,
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 @@ def save_file(self,
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 @@ def save_file(self,
md5_checksum=md5_sum
)
finally:
- 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:
+ await session.stop()
+
+ 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 @@ def get_file(self,
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 @@ def get_file(self,
self.api_id
)
- session.start()
+ await session.start()
self.media_sessions[dc_id] = session
@@ -1153,7 +1129,7 @@ def get_file(self,
file_name = ""
try:
- r = session.send(
+ r = await session.send(
functions.upload.GetFile(
location=location,
offset=offset,
@@ -1180,7 +1156,7 @@ def get_file(self,
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 @@ def get_file(self,
)
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 @@ def get_file(self,
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 @@ def get_file(self,
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 @@ def get_file(self,
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 @@ def get_file(self,
)
)
- 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 0042/1652] 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 69425c08dc9..2b597a72dfc 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -182,13 +182,12 @@ async def update_worker(self):
(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 0043/1652] 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 d06b2a76c40..0b5058e9c7d 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 f84a922c726..d45a9ee605c 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 bdda234ed98..5c19a19b6a1 100644
--- a/pyrogram/client/methods/messages/media/send_gif.py
+++ b/pyrogram/client/methods/messages/media/send_gif.py
@@ -28,19 +28,19 @@
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 0044/1652] 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 ddfc3dc2a58..26b688de25b 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -294,8 +294,12 @@ def run(self):
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 0045/1652] 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 5bc31efd342..4ca725924e5 100644
--- a/pyrogram/client/methods/bots/request_callback_answer.py
+++ b/pyrogram/client/methods/bots/request_callback_answer.py
@@ -21,10 +21,10 @@
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 @@ def request_callback_answer(self,
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 0046/1652] 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 23c2abcc04b..2025c52428e 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -265,7 +265,7 @@ async def stop(self):
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 @@ def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
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 @@ def signal_handler(*args):
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 @@ async def updates_worker(self):
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 0047/1652] Use uvloop, if available
---
pyrogram/__init__.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 531da7229fd..f6bd53210a3 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 @@
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 0048/1652] 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 f6bd53210a3..298b52c230f 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 @@
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 0049/1652] 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 2025c52428e..e60807387ea 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -288,19 +288,25 @@ def signal_handler(*args):
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 0050/1652] 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 6479dfdd079..a41ff5fab66 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -197,7 +197,7 @@ async def stop(self):
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 0051/1652] 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 8c34ea29773..61b499a38f3 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -20,6 +20,7 @@
import base64
import binascii
import getpass
+import inspect
import json
import logging
import math
@@ -328,11 +329,18 @@ def run(self, coroutine=None):
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 0052/1652] 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 10839126632..539976d66ee 100644
--- a/pyrogram/crypto/mtproto.py
+++ b/pyrogram/crypto/mtproto.py
@@ -25,8 +25,6 @@
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 ca12c92b288..981e61cda7b 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -117,7 +117,7 @@ async def start(self):
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 0053/1652] 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 47a6682a261..cbbf3c20784 100644
--- a/pyrogram/client/methods/users/delete_profile_photos.py
+++ b/pyrogram/client/methods/users/delete_profile_photos.py
@@ -24,7 +24,7 @@
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 @@ def delete_profile_photos(self, id: str or list):
)
)
- 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 0054/1652] 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 f541153eaa0..7ca479a5d51 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 @@ def close(self):
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 0055/1652] 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 7ca479a5d51..062f1d2b209 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -42,10 +42,11 @@ def __init__(self, proxy: dict):
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 0056/1652] 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 39a91db34b8..c4fc7e3a777 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1070,6 +1070,15 @@ async def save_file(self,
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 @@ async def save_file(self,
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 @@ async def save_file(self,
bytes=chunk
)
- assert await session.send(rpc), "Couldn't upload file"
+ await queue.put(rpc)
if is_missing_part:
return
@@ -1137,6 +1148,10 @@ async def save_file(self,
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 0057/1652] 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 c4fc7e3a777..cee391410fd 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1082,8 +1082,8 @@ async def worker():
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 0058/1652] 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 be4848238c7..3a27594221b 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 @@ def html(self):
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 0059/1652] 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 867cd3f6df3..fb14d379498 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -48,6 +48,7 @@
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 @@ async def authorize_user(self):
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 @@ async def authorize_user(self):
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 @@ async def authorize_user(self):
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 0060/1652] 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 fb14d379498..e56a0233215 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -585,7 +585,7 @@ async def authorize_user(self):
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 0061/1652] 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 e56a0233215..6ab9b17a1b2 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 @@ async def authorize_user(self):
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 0062/1652] 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 3a27594221b..5161d3703ba 100644
--- a/pyrogram/client/ext/utils.py
+++ b/pyrogram/client/ext/utils.py
@@ -61,7 +61,9 @@ def html(self):
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 0063/1652] 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 5161d3703ba..ae3a9763330 100644
--- a/pyrogram/client/ext/utils.py
+++ b/pyrogram/client/ext/utils.py
@@ -63,7 +63,7 @@ def html(self):
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 0064/1652] 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 5122cab9f45..635ec3751c5 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -31,7 +31,7 @@
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
-__version__ = "0.7.5"
+__version__ = "0.8.0dev1"
from .api.errors import Error
from .client.types import (
From f6886bd0e4a2bfb82fe7f7ee17e811445e4bc57d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 3 Jul 2018 16:34:55 +0200
Subject: [PATCH 0065/1652] 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 6ab9b17a1b2..8ea4b629638 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1072,14 +1072,17 @@ async def save_file(self,
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 @@ async def worker():
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 @@ async def worker():
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 0066/1652] 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 f43ca6a5176..d8f470599e8 100644
--- a/pyrogram/client/methods/messages/get_dialogs.py
+++ b/pyrogram/client/methods/messages/get_dialogs.py
@@ -24,12 +24,12 @@
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 @@ def get_dialogs(self,
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 @@ def get_dialogs(self,
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 0067/1652] 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 57d90b11268..d8eff6a4f83 100644
--- a/pyrogram/client/methods/chats/delete_chat_photo.py
+++ b/pyrogram/client/methods/chats/delete_chat_photo.py
@@ -21,7 +21,7 @@
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 @@ def delete_chat_photo(self, chat_id: int or str):
: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 a851ef584eb..295a32c552e 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 @@ def get_chat_members(self,
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 e9bc533e6eb..9e6f49c9543 100644
--- a/pyrogram/client/methods/chats/pin_chat_message.py
+++ b/pyrogram/client/methods/chats/pin_chat_message.py
@@ -21,7 +21,7 @@
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 @@ def pin_chat_message(self, chat_id: int or str, message_id: int, disable_notific
: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 c9597a62e7a..8f647818acd 100644
--- a/pyrogram/client/methods/chats/set_chat_description.py
+++ b/pyrogram/client/methods/chats/set_chat_description.py
@@ -21,7 +21,7 @@
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 @@ def set_chat_description(self, chat_id: int or str, description: str):
: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 d98fefde448..558671b7254 100644
--- a/pyrogram/client/methods/chats/set_chat_photo.py
+++ b/pyrogram/client/methods/chats/set_chat_photo.py
@@ -25,7 +25,7 @@
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 @@ def set_chat_photo(self, chat_id: int or str, photo: str):
: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 @@ def set_chat_photo(self, chat_id: int or str, photo: str):
)
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 f6644a01ca2..2769ccb92b3 100644
--- a/pyrogram/client/methods/chats/set_chat_title.py
+++ b/pyrogram/client/methods/chats/set_chat_title.py
@@ -21,7 +21,7 @@
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 @@ def set_chat_title(self, chat_id: int or str, title: str):
: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 b1eeec793d0..9b6b4144689 100644
--- a/pyrogram/client/methods/chats/unpin_chat_message.py
+++ b/pyrogram/client/methods/chats/unpin_chat_message.py
@@ -21,7 +21,7 @@
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 @@ def unpin_chat_message(self, chat_id: int or str):
: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 c9137328655..51de2a6fb26 100644
--- a/pyrogram/client/types/message.py
+++ b/pyrogram/client/types/message.py
@@ -572,7 +572,7 @@ async def click(self, x: int or str, y: int = None, quote: bool = None):
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 @@ def download(self, file_name: str = "", block: bool = True):
: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 0068/1652] 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 8d9ad00f546..13d46787828 100644
--- a/pyrogram/client/methods/chats/get_dialogs.py
+++ b/pyrogram/client/methods/chats/get_dialogs.py
@@ -22,10 +22,10 @@
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 ba68c77b969..bd9cd0fde37 100644
--- a/pyrogram/client/methods/messages/send_animation.py
+++ b/pyrogram/client/methods/messages/send_animation.py
@@ -27,21 +27,21 @@
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 0069/1652] 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 02170ab9eca..ee6372688fc 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -31,7 +31,7 @@
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
-__version__ = "0.8.0dev1"
+__version__ = "0.8.0async1"
from .api.errors import Error
from .client.types import (
From 4f9b38765e0c820b7bfd38e8731a4e2c6280b1fd Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 23 Aug 2018 21:07:19 +0200
Subject: [PATCH 0070/1652] 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 51e07f9199d..bd43808f230 100644
--- a/pyrogram/client/methods/chats/get_chat_member.py
+++ b/pyrogram/client/methods/chats/get_chat_member.py
@@ -21,7 +21,7 @@
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 @@ def get_chat_member(self,
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 @@ def get_chat_member(self,
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 086d8dc21d3..5993f1c9924 100644
--- a/pyrogram/client/methods/messages/edit_message_media.py
+++ b/pyrogram/client/methods/messages/edit_message_media.py
@@ -31,7 +31,7 @@
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 @@ def edit_message_media(self,
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 @@ def edit_message_media(self,
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 @@ def edit_message_media(self,
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 @@ def edit_message_media(self,
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 @@ def edit_message_media(self,
)
)
- 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 @@ def edit_message_media(self,
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 0071/1652] 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 34e892e01dd..fb3b559400a 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -147,7 +147,7 @@ async def start(self):
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 0072/1652] 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 fb3b559400a..fcd8f8e1733 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -401,402 +401,3 @@ async def send(self, data: Object, retries: int = MAX_RETRIES, timeout: float =
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 0073/1652] 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 9e09a8b1b37..91f3dd451fc 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -101,47 +101,3 @@ async def recv(self, length: int = 0):
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 0074/1652] 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 efe53c19ee1..11aa5d1f1c2 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 @@
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 @@ def get_chat_members_count(self, chat_id: int or str):
: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 0075/1652] 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 f191e29863c..a8aac70f014 100644
--- a/pyrogram/client/methods/users/get_me.py
+++ b/pyrogram/client/methods/users/get_me.py
@@ -30,10 +30,10 @@ async def get_me(self):
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 0076/1652] 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 55e9fea6ed7..625879003b1 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 @@ def __init__(self, ipv6: bool, proxy: dict):
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 0077/1652] 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 5566b179790..49bba1c7c1a 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 0078/1652] 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 8704247bbaf..f6a099531df 100644
--- a/pyrogram/connection/transport/tcp/tcp_full.py
+++ b/pyrogram/connection/transport/tcp/tcp_full.py
@@ -31,34 +31,33 @@ def __init__(self, ipv6: bool, proxy: dict):
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 0079/1652] 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 91ee8375ac7..c6d38153a5c 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
@@ -34,8 +34,8 @@ def __init__(self, ipv6: bool, proxy: dict):
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 @@ def connect(self, address: tuple):
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 @@ def sendall(self, data: bytes, *args):
)
)
- 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 @@ def recvall(self, length: int = 0) -> bytes or None:
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 0080/1652] 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 f0598d128bd..3aefe341a5b 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -35,8 +35,8 @@ def __init__(self, ipv6: bool, proxy: dict):
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 @@ def connect(self, address: tuple):
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 0081/1652] 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 fe8b5d5d5f8..17ed949532b 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 0082/1652] 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 17ed949532b..58bf4fdc12c 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 0083/1652] 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 095b08abcfa..61cd8c7c3b4 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -215,7 +215,7 @@ async def update_worker(self):
)
)
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 0084/1652] 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 26a0011b547..38b01fe9a1e 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -108,9 +108,9 @@ def remove_handler(self, handler, group: int):
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 0085/1652] Rework dispatcher for asyncio
---
pyrogram/client/dispatcher/dispatcher.py | 30 +++++++++++++-----------
1 file changed, 16 insertions(+), 14 deletions(-)
diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py
index 38b01fe9a1e..74ea37fd6c4 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -60,18 +60,23 @@ def __init__(self, client, workers: int):
self.updates = asyncio.Queue()
self.groups = OrderedDict()
- Dispatcher.UPDATES = {
- Dispatcher.MESSAGE_UPDATES:
- lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler),
+ 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
- Dispatcher.DELETE_MESSAGE_UPDATES:
- lambda upd, usr, cht: (utils.parse_deleted_messages(upd), DeletedMessagesHandler),
+ async def callback_query_parser(update, users, chats):
+ return await utils.parse_callback_query(self.client, update, users), CallbackQueryHandler
- Dispatcher.CALLBACK_QUERY_UPDATES:
- lambda upd, usr, cht: (utils.parse_callback_query(self.client, upd, usr), CallbackQueryHandler),
+ async def user_status_parser(update, users, chats):
+ return utils.parse_user_status(update.status, update.user_id), UserStatusHandler
- (types.UpdateUserStatus,):
- lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler)
+ Dispatcher.UPDATES = {
+ 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 @@ async def update_worker(self):
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 @@ async def update_worker(self):
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 0086/1652] 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 3dc78c607db..f01a523c537 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 0087/1652] 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 772132ce3db..ebea756a2c6 100644
--- a/pyrogram/client/methods/messages/send_message.py
+++ b/pyrogram/client/methods/messages/send_message.py
@@ -85,7 +85,7 @@ async def send_message(self,
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 0088/1652] 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 924c5df7d4f..3c0081a7b58 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1202,7 +1202,7 @@ async def worker(session):
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 @@ async def get_file(self,
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 @@ async def get_file(self,
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 0089/1652] 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 e687ced15ff..a8b5b07bc88 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 843a9bb8bf1..447d13eacb5 100644
--- a/pyrogram/client/types/bots/callback_query.py
+++ b/pyrogram/client/types/bots/callback_query.py
@@ -60,7 +60,7 @@ def __init__(
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 0090/1652] - 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 e6dcd4b663b..8a22ad7e82d 100644
--- a/pyrogram/client/dispatcher/dispatcher.py
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -72,7 +72,7 @@ async def user_status_parser(update, users, chats):
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 3e67e3cdbca..cb2dda39639 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 edcad039e35..b1ee9339623 100644
--- a/pyrogram/client/methods/messages/get_messages.py
+++ b/pyrogram/client/methods/messages/get_messages.py
@@ -78,6 +78,6 @@ async def get_messages(self,
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 390c3b2c0ff..11dd657f370 100644
--- a/pyrogram/client/methods/users/get_me.py
+++ b/pyrogram/client/methods/users/get_me.py
@@ -33,9 +33,9 @@ async def get_me(self) -> "pyrogram.User":
"""
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 c3c23333ebc..3c046cf9620 100644
--- a/pyrogram/client/types/bots/callback_query.py
+++ b/pyrogram/client/types/bots/callback_query.py
@@ -78,7 +78,7 @@ def __init__(self,
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 @@ def _parse(client, callback_query, users) -> "CallbackQuery":
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 943856818db..c84acd23916 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 @@ def __init__(self,
# 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 8f1eea9568f..ccfa89ee2be 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 0091/1652] Add async_generator requirement
---
requirements.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index f9c6874cfb5..81df21efaf1 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 0092/1652] 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 fb4552b7614..2dacc26ed5a 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -321,7 +321,7 @@ async def stop(self):
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 4d5974252b9..7d2f44e338b 100644
--- a/pyrogram/client/methods/chats/get_dialogs.py
+++ b/pyrogram/client/methods/chats/get_dialogs.py
@@ -78,4 +78,4 @@ async def get_dialogs(self,
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 bdd8d1177cc..f521ebc6be7 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 @@ class Filters:
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 @@ def iter_chat_members(self,
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 @@ def iter_chat_members(self,
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 6058cd17b0a..712254e9efb 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 @@ def iter_dialogs(self,
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 @@ def iter_dialogs(self,
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 @@ def iter_dialogs(self,
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 ab5879885a6..1a97799864e 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 @@ def iter_history(self,
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 @@ def iter_history(self,
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 5b12da45434..a48b6acf4fc 100644
--- a/pyrogram/client/types/messages_and_media/messages.py
+++ b/pyrogram/client/types/messages_and_media/messages.py
@@ -65,7 +65,7 @@ async def _parse(client, messages: types.messages.Messages, replies: int = 1) ->
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 394ddd28bca..b3c2d773c02 100644
--- a/pyrogram/client/types/user_and_chats/dialogs.py
+++ b/pyrogram/client/types/user_and_chats/dialogs.py
@@ -47,7 +47,7 @@ def __init__(self,
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 @@ def _parse(client, dialogs) -> "Dialogs":
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 0093/1652] 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 45bab7388b9..7d5e7a4b0bf 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -96,29 +96,29 @@ def __init__(self):
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 ad4f8b4a0d6..e58bc17e037 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 @@
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 @@ def get_game_high_scores(self,
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 401a5aa6867..0f3593b0580 100644
--- a/pyrogram/client/methods/bots/send_game.py
+++ b/pyrogram/client/methods/bots/send_game.py
@@ -24,15 +24,15 @@
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 @@ def send_game(self,
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 @@ def send_game(self,
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 e9d2084477c..f90b7f46fa4 100644
--- a/pyrogram/client/methods/bots/set_game_score.py
+++ b/pyrogram/client/methods/bots/set_game_score.py
@@ -24,13 +24,13 @@
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 @@ def set_game_score(self,
: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 @@ def set_game_score(self,
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 772814255f9..f17eac930de 100644
--- a/pyrogram/client/types/messages_and_media/message.py
+++ b/pyrogram/client/types/messages_and_media/message.py
@@ -423,7 +423,7 @@ async def _parse(client, message: types.Message or types.MessageService or types
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 @@ async def click(self, x: int or str, y: int = None, quote: bool = None):
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 0094/1652] 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 1bf0255b0c0..44d7d137367 100644
--- a/pyrogram/client/methods/messages/send_animation.py
+++ b/pyrogram/client/methods/messages/send_animation.py
@@ -177,25 +177,25 @@ async def send_animation(self,
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 ea23f8d385b..e333dee41d5 100644
--- a/pyrogram/client/methods/messages/send_audio.py
+++ b/pyrogram/client/methods/messages/send_audio.py
@@ -176,25 +176,25 @@ async def send_audio(self,
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 f60f7815e8c..e9ab9375928 100644
--- a/pyrogram/client/methods/messages/send_document.py
+++ b/pyrogram/client/methods/messages/send_document.py
@@ -157,25 +157,25 @@ async def send_document(self,
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 d734ee35579..267a71362ae 100644
--- a/pyrogram/client/methods/messages/send_photo.py
+++ b/pyrogram/client/methods/messages/send_photo.py
@@ -153,25 +153,25 @@ async def send_photo(self,
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 172d7c80b48..7525e2f9349 100644
--- a/pyrogram/client/methods/messages/send_sticker.py
+++ b/pyrogram/client/methods/messages/send_sticker.py
@@ -137,25 +137,25 @@ async def send_sticker(self,
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 ff6020df981..3c202b55dc9 100644
--- a/pyrogram/client/methods/messages/send_video.py
+++ b/pyrogram/client/methods/messages/send_video.py
@@ -180,25 +180,25 @@ async def send_video(self,
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 712c09f10dc..fbc6c98407c 100644
--- a/pyrogram/client/methods/messages/send_video_note.py
+++ b/pyrogram/client/methods/messages/send_video_note.py
@@ -155,25 +155,25 @@ async def send_video_note(self,
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 458192b8f49..8b6c8e61379 100644
--- a/pyrogram/client/methods/messages/send_voice.py
+++ b/pyrogram/client/methods/messages/send_voice.py
@@ -156,25 +156,25 @@ async def send_voice(self,
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 0095/1652] 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 c868acb524f..422cc34d77c 100644
--- a/pyrogram/client/methods/chats/get_chat.py
+++ b/pyrogram/client/methods/chats/get_chat.py
@@ -73,4 +73,4 @@ async def get_chat(self,
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 ec30b866dc1..de1cd633915 100644
--- a/pyrogram/client/types/user_and_chats/chat.py
+++ b/pyrogram/client/types/user_and_chats/chat.py
@@ -174,7 +174,7 @@ def _parse_dialog(client, peer, users: dict, chats: dict):
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 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) ->
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 0096/1652] 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 15d7637b68c..d33b5bb9953 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -105,10 +105,10 @@ async def send(self, *args, **kwargs):
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 0097/1652] 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 80b719af24a..d7414530aac 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -126,5 +126,5 @@ async def get_dialogs(self, *args, **kwargs):
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 d1ac8d6af0e..ce923b658b6 100644
--- a/pyrogram/client/methods/chats/iter_chat_members.py
+++ b/pyrogram/client/methods/chats/iter_chat_members.py
@@ -88,7 +88,7 @@ async def iter_chat_members(self,
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 0098/1652] 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 3c202b55dc9..aecffe288af 100644
--- a/pyrogram/client/methods/messages/send_video.py
+++ b/pyrogram/client/methods/messages/send_video.py
@@ -133,7 +133,7 @@ async def send_video(self,
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 0099/1652] 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 e333dee41d5..73208b25c84 100644
--- a/pyrogram/client/methods/messages/send_audio.py
+++ b/pyrogram/client/methods/messages/send_audio.py
@@ -130,7 +130,7 @@ async def send_audio(self,
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 0100/1652] 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 863856f35af..267aade4922 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -48,7 +48,6 @@
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 @@ async def default_phone_number_callback():
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 @@ async def default_recovery_callback(email_pattern: str) -> str:
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 @@ async def default_recovery_callback(email_pattern: str) -> str:
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 7d2f44e338b..aa6ca91236b 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 @@ async def get_dialogs(self,
)
)
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 ae9925eb860..88e69244608 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 09a132b6b84..a7b9b751659 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 @@ async def get_messages(self,
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 17b22d6fe1c..f966705fac0 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 @@ async def create(self):
else:
raise e
- time.sleep(1)
+ await asyncio.sleep(1)
continue
else:
return auth_key
From 9d32b28f94e4c0f263e80e758fa530a3471cf91b Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Thu, 21 Feb 2019 20:12:11 +0300
Subject: [PATCH 0101/1652] Implement extendable session storage and JSON
session storage
---
pyrogram/client/client.py | 59 ++-------
pyrogram/client/ext/base_client.py | 14 +--
pyrogram/client/ext/syncer.py | 41 +------
pyrogram/client/session_storage/__init__.py | 21 ++++
.../session_storage/base_session_storage.py | 50 ++++++++
.../session_storage/json_session_storage.py | 116 ++++++++++++++++++
.../session_storage/session_storage_mixin.py | 73 +++++++++++
7 files changed, 278 insertions(+), 96 deletions(-)
create mode 100644 pyrogram/client/session_storage/__init__.py
create mode 100644 pyrogram/client/session_storage/base_session_storage.py
create mode 100644 pyrogram/client/session_storage/json_session_storage.py
create mode 100644 pyrogram/client/session_storage/session_storage_mixin.py
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index f62c046c403..9a9f8482599 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -36,7 +36,7 @@
from pathlib import Path
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Thread
-from typing import Union, List
+from typing import Union, List, Type
from pyrogram.api import functions, types
from pyrogram.api.core import Object
@@ -56,6 +56,7 @@
from .dispatcher import Dispatcher
from .ext import utils, Syncer, BaseClient
from .methods import Methods
+from .session_storage import BaseSessionStorage, JsonSessionStorage, SessionDoesNotExist
log = logging.getLogger(__name__)
@@ -199,8 +200,9 @@ def __init__(self,
config_file: str = BaseClient.CONFIG_FILE,
plugins: dict = None,
no_updates: bool = None,
- takeout: bool = None):
- super().__init__()
+ takeout: bool = None,
+ session_storage_cls: Type[BaseSessionStorage] = JsonSessionStorage):
+ super().__init__(session_storage_cls(self))
self.session_name = session_name
self.api_id = int(api_id) if api_id else None
@@ -296,8 +298,8 @@ def start(self):
now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP:
- self.peers_by_username = {}
- self.peers_by_phone = {}
+ self.peers_by_username.clear()
+ self.peers_by_phone.clear()
self.get_initial_dialogs()
self.get_contacts()
@@ -1101,33 +1103,10 @@ def load_config(self):
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.session_storage.load_session(self.session_name)
+ except SessionDoesNotExist:
+ log.info('Session {} was not found, initializing new one')
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
- else:
- self.dc_id = s["dc_id"]
- self.test_mode = s["test_mode"]
- self.auth_key = base64.b64decode("".join(s["auth_key"]))
- self.user_id = s["user_id"]
- self.date = s.get("date", 0)
-
- for k, v in s.get("peers_by_id", {}).items():
- self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
-
- for k, v in s.get("peers_by_username", {}).items():
- peer = self.peers_by_id.get(v, None)
-
- if peer:
- self.peers_by_username[k] = peer
-
- for k, v in s.get("peers_by_phone", {}).items():
- peer = self.peers_by_id.get(v, None)
-
- if peer:
- self.peers_by_phone[k] = peer
def load_plugins(self):
if self.plugins.get("enabled", False):
@@ -1234,23 +1213,7 @@ def load_plugins(self):
log.warning('No plugin loaded from "{}"'.format(root))
def save_session(self):
- auth_key = base64.b64encode(self.auth_key).decode()
- auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
-
- os.makedirs(self.workdir, exist_ok=True)
-
- with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f:
- json.dump(
- dict(
- dc_id=self.dc_id,
- test_mode=self.test_mode,
- auth_key=auth_key,
- user_id=self.user_id,
- date=self.date
- ),
- f,
- indent=4
- )
+ self.session_storage.save_session(self.session_name)
def get_initial_dialogs_chunk(self,
offset_date: int = 0):
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index d2c348a8111..87f11e23ae1 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -24,9 +24,10 @@
from pyrogram import __version__
from ..style import Markdown, HTML
from ...session.internals import MsgId
+from ..session_storage import SessionStorageMixin, BaseSessionStorage
-class BaseClient:
+class BaseClient(SessionStorageMixin):
class StopTransmission(StopIteration):
pass
@@ -67,20 +68,13 @@ class StopTransmission(StopIteration):
13: "video_note"
}
- def __init__(self):
+ def __init__(self, session_storage: BaseSessionStorage):
+ self.session_storage = session_storage
self.bot_token = None
- self.dc_id = None
- self.auth_key = None
- self.user_id = None
- self.date = None
self.rnd_id = MsgId
self.channels_pts = {}
- self.peers_by_id = {}
- self.peers_by_username = {}
- self.peers_by_phone = {}
-
self.markdown = Markdown(self.peers_by_id)
self.html = HTML(self.peers_by_id)
diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py
index e169d2a33d3..8930b13ea43 100644
--- a/pyrogram/client/ext/syncer.py
+++ b/pyrogram/client/ext/syncer.py
@@ -81,47 +81,12 @@ def worker(cls):
@classmethod
def sync(cls, client):
- temporary = os.path.join(client.workdir, "{}.sync".format(client.session_name))
- persistent = os.path.join(client.workdir, "{}.session".format(client.session_name))
-
+ client.date = int(time.time())
try:
- auth_key = base64.b64encode(client.auth_key).decode()
- auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
-
- data = dict(
- dc_id=client.dc_id,
- test_mode=client.test_mode,
- auth_key=auth_key,
- user_id=client.user_id,
- date=int(time.time()),
- peers_by_id={
- k: getattr(v, "access_hash", None)
- for k, v in client.peers_by_id.copy().items()
- },
- peers_by_username={
- k: utils.get_peer_id(v)
- for k, v in client.peers_by_username.copy().items()
- },
- peers_by_phone={
- k: utils.get_peer_id(v)
- for k, v in client.peers_by_phone.copy().items()
- }
- )
-
- os.makedirs(client.workdir, exist_ok=True)
-
- with open(temporary, "w", encoding="utf-8") as f:
- json.dump(data, f, indent=4)
-
- f.flush()
- os.fsync(f.fileno())
+ client.session_storage.save_session(client.session_name, sync=True)
except Exception as e:
log.critical(e, exc_info=True)
else:
- shutil.move(temporary, persistent)
log.info("Synced {}".format(client.session_name))
finally:
- try:
- os.remove(temporary)
- except OSError:
- pass
+ client.session_storage.sync_cleanup(client.session_name)
diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py
new file mode 100644
index 00000000000..6ee92ebc2c6
--- /dev/null
+++ b/pyrogram/client/session_storage/__init__.py
@@ -0,0 +1,21 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2019 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 .session_storage_mixin import SessionStorageMixin
+from .base_session_storage import BaseSessionStorage, SessionDoesNotExist
+from .json_session_storage import JsonSessionStorage
diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py
new file mode 100644
index 00000000000..75e416b4253
--- /dev/null
+++ b/pyrogram/client/session_storage/base_session_storage.py
@@ -0,0 +1,50 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2019 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 .
+
+import abc
+
+import pyrogram
+
+
+class SessionDoesNotExist(Exception):
+ pass
+
+
+class BaseSessionStorage(abc.ABC):
+ def __init__(self, client: 'pyrogram.client.BaseClient'):
+ self.client = client
+ self.dc_id = 1
+ self.test_mode = None
+ self.auth_key = None
+ self.user_id = None
+ self.date = 0
+ self.peers_by_id = {}
+ self.peers_by_username = {}
+ self.peers_by_phone = {}
+
+ @abc.abstractmethod
+ def load_session(self, name: str):
+ ...
+
+ @abc.abstractmethod
+ def save_session(self, name: str, sync=False):
+ ...
+
+ @abc.abstractmethod
+ def sync_cleanup(self, name: str):
+ ...
diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py
new file mode 100644
index 00000000000..679a21f357c
--- /dev/null
+++ b/pyrogram/client/session_storage/json_session_storage.py
@@ -0,0 +1,116 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2019 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 .
+
+import base64
+import json
+import logging
+import os
+import shutil
+
+from ..ext import utils
+from . import BaseSessionStorage, SessionDoesNotExist
+
+
+log = logging.getLogger(__name__)
+
+
+class JsonSessionStorage(BaseSessionStorage):
+ def _get_file_name(self, name: str):
+ if not name.endswith('.session'):
+ name += '.session'
+ return os.path.join(self.client.workdir, name)
+
+ def load_session(self, name: str):
+ file_path = self._get_file_name(name)
+ log.info('Loading JSON session from {}'.format(file_path))
+
+ try:
+ with open(file_path, encoding='utf-8') as f:
+ s = json.load(f)
+ except FileNotFoundError:
+ raise SessionDoesNotExist()
+
+ self.dc_id = s["dc_id"]
+ self.test_mode = s["test_mode"]
+ self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key
+ self.user_id = s["user_id"]
+ self.date = s.get("date", 0)
+
+ for k, v in s.get("peers_by_id", {}).items():
+ self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
+
+ for k, v in s.get("peers_by_username", {}).items():
+ peer = self.peers_by_id.get(v, None)
+
+ if peer:
+ self.peers_by_username[k] = peer
+
+ for k, v in s.get("peers_by_phone", {}).items():
+ peer = self.peers_by_id.get(v, None)
+
+ if peer:
+ self.peers_by_phone[k] = peer
+
+ def save_session(self, name: str, sync=False):
+ file_path = self._get_file_name(name)
+
+ if sync:
+ file_path += '.tmp'
+
+ log.info('Saving JSON session to {}, sync={}'.format(file_path, sync))
+
+ auth_key = base64.b64encode(self.auth_key).decode()
+ auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars
+
+ os.makedirs(self.client.workdir, exist_ok=True)
+
+ data = {
+ 'dc_id': self.dc_id,
+ 'test_mode': self.test_mode,
+ 'auth_key': auth_key,
+ 'user_id': self.user_id,
+ 'date': self.date,
+ 'peers_by_id': {
+ k: getattr(v, "access_hash", None)
+ for k, v in self.peers_by_id.copy().items()
+ },
+ 'peers_by_username': {
+ k: utils.get_peer_id(v)
+ for k, v in self.peers_by_username.copy().items()
+ },
+ 'peers_by_phone': {
+ k: utils.get_peer_id(v)
+ for k, v in self.peers_by_phone.copy().items()
+ }
+ }
+
+ with open(file_path, "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=4)
+
+ f.flush()
+ os.fsync(f.fileno())
+
+ # execution won't be here if an error has occurred earlier
+ if sync:
+ shutil.move(file_path, self._get_file_name(name))
+
+ def sync_cleanup(self, name: str):
+ try:
+ os.remove(self._get_file_name(name) + '.tmp')
+ except OSError:
+ pass
diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/session_storage_mixin.py
new file mode 100644
index 00000000000..bfe9a59026b
--- /dev/null
+++ b/pyrogram/client/session_storage/session_storage_mixin.py
@@ -0,0 +1,73 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2019 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 typing import Dict
+
+
+class SessionStorageMixin:
+ @property
+ def dc_id(self) -> int:
+ return self.session_storage.dc_id
+
+ @dc_id.setter
+ def dc_id(self, val):
+ self.session_storage.dc_id = val
+
+ @property
+ def test_mode(self) -> bool:
+ return self.session_storage.test_mode
+
+ @test_mode.setter
+ def test_mode(self, val):
+ self.session_storage.test_mode = val
+
+ @property
+ def auth_key(self) -> bytes:
+ return self.session_storage.auth_key
+
+ @auth_key.setter
+ def auth_key(self, val):
+ self.session_storage.auth_key = val
+
+ @property
+ def user_id(self):
+ return self.session_storage.user_id
+
+ @user_id.setter
+ def user_id(self, val) -> int:
+ self.session_storage.user_id = val
+
+ @property
+ def date(self) -> int:
+ return self.session_storage.date
+
+ @date.setter
+ def date(self, val):
+ self.session_storage.date = val
+
+ @property
+ def peers_by_id(self) -> Dict[str, int]:
+ return self.session_storage.peers_by_id
+
+ @property
+ def peers_by_username(self) -> Dict[str, int]:
+ return self.session_storage.peers_by_username
+
+ @property
+ def peers_by_phone(self) -> Dict[str, int]:
+ return self.session_storage.peers_by_phone
From 431a983d5b66522604f0685ef078d40735cea64c Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Thu, 21 Feb 2019 21:18:53 +0300
Subject: [PATCH 0102/1652] Fix logging and cleanup imports in client.py
---
pyrogram/client/client.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 9a9f8482599..0e8d5554212 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -16,9 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-import base64
import binascii
-import json
import logging
import math
import mimetypes
@@ -1105,7 +1103,10 @@ def load_session(self):
try:
self.session_storage.load_session(self.session_name)
except SessionDoesNotExist:
- log.info('Session {} was not found, initializing new one')
+ session_name = self.session_name[:32]
+ if session_name != self.session_name:
+ session_name += '...'
+ log.info('Could not load session "{}", initializing new one'.format(self.session_name))
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
def load_plugins(self):
From b04cf9ec9297ce0884943879b1fac07fa7e2933f Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Thu, 21 Feb 2019 21:43:57 +0300
Subject: [PATCH 0103/1652] Add string session storage
---
pyrogram/client/session_storage/__init__.py | 1 +
.../session_storage/string_session_storage.py | 38 +++++++++++++++++++
2 files changed, 39 insertions(+)
create mode 100644 pyrogram/client/session_storage/string_session_storage.py
diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py
index 6ee92ebc2c6..ced103ce2ef 100644
--- a/pyrogram/client/session_storage/__init__.py
+++ b/pyrogram/client/session_storage/__init__.py
@@ -19,3 +19,4 @@
from .session_storage_mixin import SessionStorageMixin
from .base_session_storage import BaseSessionStorage, SessionDoesNotExist
from .json_session_storage import JsonSessionStorage
+from .string_session_storage import StringSessionStorage
diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py
new file mode 100644
index 00000000000..9b6ebf0eb3f
--- /dev/null
+++ b/pyrogram/client/session_storage/string_session_storage.py
@@ -0,0 +1,38 @@
+import base64
+import binascii
+import struct
+
+from . import BaseSessionStorage, SessionDoesNotExist
+
+
+def StringSessionStorage(print_session: bool = False):
+ class StringSessionStorageClass(BaseSessionStorage):
+ """
+ Packs session data as following (forcing little-endian byte order):
+ Char dc_id (1 byte, unsigned)
+ Boolean test_mode (1 byte)
+ Long long user_id (8 bytes, signed)
+ Bytes auth_key (256 bytes)
+
+ Uses Base64 encoding for printable representation
+ """
+ PACK_FORMAT = '
Date: Fri, 22 Feb 2019 00:03:58 +0300
Subject: [PATCH 0104/1652] Refactor session storages: use session_name arg to
detect storage type
---
pyrogram/client/client.py | 34 +++++++----
pyrogram/client/ext/syncer.py | 4 +-
pyrogram/client/session_storage/__init__.py | 4 +-
.../session_storage/base_session_storage.py | 17 ++++--
.../session_storage/json_session_storage.py | 14 ++---
.../session_storage/string_session_storage.py | 61 +++++++++----------
6 files changed, 75 insertions(+), 59 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 0e8d5554212..f17a054b235 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -49,12 +49,15 @@
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.client.methods.password.utils import compute_check
+from pyrogram.client.session_storage import BaseSessionConfig
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher
from .ext import utils, Syncer, BaseClient
from .methods import Methods
-from .session_storage import BaseSessionStorage, JsonSessionStorage, SessionDoesNotExist
+from .session_storage import SessionDoesNotExist
+from .session_storage.json_session_storage import JsonSessionStorage
+from .session_storage.string_session_storage import StringSessionStorage
log = logging.getLogger(__name__)
@@ -176,7 +179,7 @@ class Client(Methods, BaseClient):
"""
def __init__(self,
- session_name: str,
+ session_name: Union[str, BaseSessionConfig],
api_id: Union[int, str] = None,
api_hash: str = None,
app_version: str = None,
@@ -198,11 +201,21 @@ def __init__(self,
config_file: str = BaseClient.CONFIG_FILE,
plugins: dict = None,
no_updates: bool = None,
- takeout: bool = None,
- session_storage_cls: Type[BaseSessionStorage] = JsonSessionStorage):
- super().__init__(session_storage_cls(self))
+ takeout: bool = None):
- self.session_name = session_name
+ if isinstance(session_name, str):
+ if session_name.startswith(':'):
+ session_storage = StringSessionStorage(self, session_name)
+ else:
+ session_storage = JsonSessionStorage(self, session_name)
+ elif isinstance(session_name, BaseSessionConfig):
+ session_storage = session_name.session_storage_cls(self, session_name)
+ else:
+ raise RuntimeError('Wrong session_name passed, expected str or BaseSessionConfig subclass')
+
+ super().__init__(session_storage)
+
+ self.session_name = str(session_name) # TODO: build correct session name
self.api_id = int(api_id) if api_id else None
self.api_hash = api_hash
self.app_version = app_version
@@ -1101,12 +1114,9 @@ def load_config(self):
def load_session(self):
try:
- self.session_storage.load_session(self.session_name)
+ self.session_storage.load_session()
except SessionDoesNotExist:
- session_name = self.session_name[:32]
- if session_name != self.session_name:
- session_name += '...'
- log.info('Could not load session "{}", initializing new one'.format(self.session_name))
+ log.info('Could not load session "{}", initiate new one'.format(self.session_name))
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
def load_plugins(self):
@@ -1214,7 +1224,7 @@ def load_plugins(self):
log.warning('No plugin loaded from "{}"'.format(root))
def save_session(self):
- self.session_storage.save_session(self.session_name)
+ self.session_storage.save_session()
def get_initial_dialogs_chunk(self,
offset_date: int = 0):
diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py
index 8930b13ea43..709556245df 100644
--- a/pyrogram/client/ext/syncer.py
+++ b/pyrogram/client/ext/syncer.py
@@ -83,10 +83,10 @@ def worker(cls):
def sync(cls, client):
client.date = int(time.time())
try:
- client.session_storage.save_session(client.session_name, sync=True)
+ client.session_storage.save_session(sync=True)
except Exception as e:
log.critical(e, exc_info=True)
else:
log.info("Synced {}".format(client.session_name))
finally:
- client.session_storage.sync_cleanup(client.session_name)
+ client.session_storage.sync_cleanup()
diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py
index ced103ce2ef..611ec9b7441 100644
--- a/pyrogram/client/session_storage/__init__.py
+++ b/pyrogram/client/session_storage/__init__.py
@@ -17,6 +17,4 @@
# along with Pyrogram. If not, see .
from .session_storage_mixin import SessionStorageMixin
-from .base_session_storage import BaseSessionStorage, SessionDoesNotExist
-from .json_session_storage import JsonSessionStorage
-from .string_session_storage import StringSessionStorage
+from .base_session_storage import BaseSessionStorage, BaseSessionConfig, SessionDoesNotExist
diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py
index 75e416b4253..a5c879f1fe8 100644
--- a/pyrogram/client/session_storage/base_session_storage.py
+++ b/pyrogram/client/session_storage/base_session_storage.py
@@ -17,6 +17,7 @@
# along with Pyrogram. If not, see .
import abc
+from typing import Type
import pyrogram
@@ -26,8 +27,9 @@ class SessionDoesNotExist(Exception):
class BaseSessionStorage(abc.ABC):
- def __init__(self, client: 'pyrogram.client.BaseClient'):
+ def __init__(self, client: 'pyrogram.client.BaseClient', session_data):
self.client = client
+ self.session_data = session_data
self.dc_id = 1
self.test_mode = None
self.auth_key = None
@@ -38,13 +40,20 @@ def __init__(self, client: 'pyrogram.client.BaseClient'):
self.peers_by_phone = {}
@abc.abstractmethod
- def load_session(self, name: str):
+ def load_session(self):
...
@abc.abstractmethod
- def save_session(self, name: str, sync=False):
+ def save_session(self, sync=False):
...
@abc.abstractmethod
- def sync_cleanup(self, name: str):
+ def sync_cleanup(self):
+ ...
+
+
+class BaseSessionConfig(abc.ABC):
+ @property
+ @abc.abstractmethod
+ def session_storage_cls(self) -> Type[BaseSessionStorage]:
...
diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py
index 679a21f357c..f41091af7eb 100644
--- a/pyrogram/client/session_storage/json_session_storage.py
+++ b/pyrogram/client/session_storage/json_session_storage.py
@@ -35,8 +35,8 @@ def _get_file_name(self, name: str):
name += '.session'
return os.path.join(self.client.workdir, name)
- def load_session(self, name: str):
- file_path = self._get_file_name(name)
+ def load_session(self):
+ file_path = self._get_file_name(self.session_data)
log.info('Loading JSON session from {}'.format(file_path))
try:
@@ -66,8 +66,8 @@ def load_session(self, name: str):
if peer:
self.peers_by_phone[k] = peer
- def save_session(self, name: str, sync=False):
- file_path = self._get_file_name(name)
+ def save_session(self, sync=False):
+ file_path = self._get_file_name(self.session_data)
if sync:
file_path += '.tmp'
@@ -107,10 +107,10 @@ def save_session(self, name: str, sync=False):
# execution won't be here if an error has occurred earlier
if sync:
- shutil.move(file_path, self._get_file_name(name))
+ shutil.move(file_path, self._get_file_name(self.session_data))
- def sync_cleanup(self, name: str):
+ def sync_cleanup(self):
try:
- os.remove(self._get_file_name(name) + '.tmp')
+ os.remove(self._get_file_name(self.session_data) + '.tmp')
except OSError:
pass
diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py
index 9b6ebf0eb3f..c01a2b355d4 100644
--- a/pyrogram/client/session_storage/string_session_storage.py
+++ b/pyrogram/client/session_storage/string_session_storage.py
@@ -5,34 +5,33 @@
from . import BaseSessionStorage, SessionDoesNotExist
-def StringSessionStorage(print_session: bool = False):
- class StringSessionStorageClass(BaseSessionStorage):
- """
- Packs session data as following (forcing little-endian byte order):
- Char dc_id (1 byte, unsigned)
- Boolean test_mode (1 byte)
- Long long user_id (8 bytes, signed)
- Bytes auth_key (256 bytes)
-
- Uses Base64 encoding for printable representation
- """
- PACK_FORMAT = '
Date: Fri, 22 Feb 2019 01:34:08 +0300
Subject: [PATCH 0105/1652] Add bot_token argument (closes #123)
---
pyrogram/client/client.py | 25 ++++++++++++++++++++-----
pyrogram/client/ext/base_client.py | 2 +-
pyrogram/client/ext/syncer.py | 1 +
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index f62c046c403..da2ddc5b273 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -29,6 +29,7 @@
import tempfile
import threading
import time
+import warnings
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
@@ -67,9 +68,8 @@ class Client(Methods, BaseClient):
Args:
session_name (``str``):
- Name to uniquely identify a session of either a User or a Bot.
- For Users: pass a string of your choice, e.g.: "my_main_account".
- For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+ Name to uniquely identify a session of either a User or a Bot, e.g.: "my_main_account".
+ You still can use bot token here, but it will be deprecated in next release.
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
api_id (``int``, *optional*):
@@ -144,6 +144,10 @@ class Client(Methods, BaseClient):
a new Telegram account in case the phone number you passed is not registered yet.
Only applicable for new sessions.
+ bot_token (``str``, *optional*):
+ Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+ Only applicable for new sessions.
+
last_name (``str``, *optional*):
Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can
be an empty string: "". Only applicable for new sessions.
@@ -192,6 +196,7 @@ def __init__(self,
password: str = None,
recovery_code: callable = None,
force_sms: bool = False,
+ bot_token: str = None,
first_name: str = None,
last_name: str = None,
workers: int = BaseClient.WORKERS,
@@ -218,6 +223,7 @@ def __init__(self,
self.password = password
self.recovery_code = recovery_code
self.force_sms = force_sms
+ self.bot_token = bot_token
self.first_name = first_name
self.last_name = last_name
self.workers = workers
@@ -263,8 +269,13 @@ def start(self):
raise ConnectionError("Client has already been started")
if self.BOT_TOKEN_RE.match(self.session_name):
+ self.is_bot = True
self.bot_token = self.session_name
self.session_name = self.session_name.split(":")[0]
+ warnings.warn('\nYou are using a bot token as session name.\n'
+ 'It will be deprecated in next update, please use session file name to load '
+ 'existing sessions and bot_token argument to create new sessions.',
+ DeprecationWarning, stacklevel=2)
self.load_config()
self.load_session()
@@ -284,11 +295,12 @@ def start(self):
if self.bot_token is None:
self.authorize_user()
else:
+ self.is_bot = True
self.authorize_bot()
self.save_session()
- if self.bot_token is None:
+ if not self.is_bot:
if self.takeout:
self.takeout_id = self.send(functions.account.InitTakeoutSession()).id
log.warning("Takeout session {} initiated".format(self.takeout_id))
@@ -1113,6 +1125,8 @@ def load_session(self):
self.auth_key = base64.b64decode("".join(s["auth_key"]))
self.user_id = s["user_id"]
self.date = s.get("date", 0)
+ # TODO: replace default with False once token session name will be deprecated
+ self.is_bot = s.get("is_bot", self.is_bot)
for k, v in s.get("peers_by_id", {}).items():
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
@@ -1246,7 +1260,8 @@ def save_session(self):
test_mode=self.test_mode,
auth_key=auth_key,
user_id=self.user_id,
- date=self.date
+ date=self.date,
+ is_bot=self.is_bot,
),
f,
indent=4
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index d2c348a8111..8ca784aac5e 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -68,7 +68,7 @@ class StopTransmission(StopIteration):
}
def __init__(self):
- self.bot_token = None
+ self.is_bot = False
self.dc_id = None
self.auth_key = None
self.user_id = None
diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py
index e169d2a33d3..71dc3f353a5 100644
--- a/pyrogram/client/ext/syncer.py
+++ b/pyrogram/client/ext/syncer.py
@@ -94,6 +94,7 @@ def sync(cls, client):
auth_key=auth_key,
user_id=client.user_id,
date=int(time.time()),
+ is_bot=client.is_bot,
peers_by_id={
k: getattr(v, "access_hash", None)
for k, v in client.peers_by_id.copy().items()
From 9c4e9e166e528d2ef990bcb3f2093a877d65b642 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Fri, 22 Feb 2019 02:13:51 +0300
Subject: [PATCH 0106/1652] Merge #221, string sessions now work for bots too
---
pyrogram/client/client.py | 17 +++++++++--------
pyrogram/client/ext/base_client.py | 1 -
.../session_storage/base_session_storage.py | 1 +
.../session_storage/json_session_storage.py | 2 ++
.../session_storage/session_storage_mixin.py | 8 ++++++++
.../session_storage/string_session_storage.py | 13 ++++++++++---
6 files changed, 30 insertions(+), 12 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 42a2566a2d4..429abab3ed0 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -281,14 +281,15 @@ def start(self):
if self.is_started:
raise ConnectionError("Client has already been started")
- if self.BOT_TOKEN_RE.match(self.session_name):
- self.is_bot = True
- self.bot_token = self.session_name
- self.session_name = self.session_name.split(":")[0]
- warnings.warn('\nYou are using a bot token as session name.\n'
- 'It will be deprecated in next update, please use session file name to load '
- 'existing sessions and bot_token argument to create new sessions.',
- DeprecationWarning, stacklevel=2)
+ if isinstance(self.session_storage, JsonSessionStorage):
+ if self.BOT_TOKEN_RE.match(self.session_storage.session_data):
+ self.is_bot = True
+ self.bot_token = self.session_storage.session_data
+ self.session_storage.session_data = self.session_storage.session_data.split(":")[0]
+ warnings.warn('\nYou are using a bot token as session name.\n'
+ 'It will be deprecated in next update, please use session file name to load '
+ 'existing sessions and bot_token argument to create new sessions.',
+ DeprecationWarning, stacklevel=2)
self.load_config()
self.load_session()
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index a354ba760f6..3f40865ff4d 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -70,7 +70,6 @@ class StopTransmission(StopIteration):
def __init__(self, session_storage: BaseSessionStorage):
self.session_storage = session_storage
- self.is_bot = False
self.rnd_id = MsgId
self.channels_pts = {}
diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py
index a5c879f1fe8..92473956c5e 100644
--- a/pyrogram/client/session_storage/base_session_storage.py
+++ b/pyrogram/client/session_storage/base_session_storage.py
@@ -35,6 +35,7 @@ def __init__(self, client: 'pyrogram.client.BaseClient', session_data):
self.auth_key = None
self.user_id = None
self.date = 0
+ self.is_bot = False
self.peers_by_id = {}
self.peers_by_username = {}
self.peers_by_phone = {}
diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json_session_storage.py
index f41091af7eb..1e1e0ca4a07 100644
--- a/pyrogram/client/session_storage/json_session_storage.py
+++ b/pyrogram/client/session_storage/json_session_storage.py
@@ -50,6 +50,7 @@ def load_session(self):
self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key
self.user_id = s["user_id"]
self.date = s.get("date", 0)
+ self.is_bot = s.get('is_bot', self.client.is_bot)
for k, v in s.get("peers_by_id", {}).items():
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
@@ -85,6 +86,7 @@ def save_session(self, sync=False):
'auth_key': auth_key,
'user_id': self.user_id,
'date': self.date,
+ 'is_bot': self.is_bot,
'peers_by_id': {
k: getattr(v, "access_hash", None)
for k, v in self.peers_by_id.copy().items()
diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/session_storage_mixin.py
index bfe9a59026b..7d783ca72fb 100644
--- a/pyrogram/client/session_storage/session_storage_mixin.py
+++ b/pyrogram/client/session_storage/session_storage_mixin.py
@@ -60,6 +60,14 @@ def date(self) -> int:
def date(self, val):
self.session_storage.date = val
+ @property
+ def is_bot(self):
+ return self.session_storage.is_bot
+
+ @is_bot.setter
+ def is_bot(self, val) -> int:
+ self.session_storage.is_bot = val
+
@property
def peers_by_id(self) -> Dict[str, int]:
return self.session_storage.peers_by_id
diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string_session_storage.py
index c01a2b355d4..5b1a8cc1ae8 100644
--- a/pyrogram/client/session_storage/string_session_storage.py
+++ b/pyrogram/client/session_storage/string_session_storage.py
@@ -11,24 +11,31 @@ class StringSessionStorage(BaseSessionStorage):
Char dc_id (1 byte, unsigned)
Boolean test_mode (1 byte)
Long long user_id (8 bytes, signed)
+ Boolean is_bot (1 byte)
Bytes auth_key (256 bytes)
Uses Base64 encoding for printable representation
"""
- PACK_FORMAT = '
Date: Fri, 22 Feb 2019 03:37:19 +0300
Subject: [PATCH 0107/1652] add in-memory session storage, refactor session
storages, remove mixin
---
pyrogram/client/client.py | 112 +++++++++---------
pyrogram/client/ext/base_client.py | 10 +-
pyrogram/client/ext/syncer.py | 4 +-
.../client/methods/contacts/get_contacts.py | 2 +-
pyrogram/client/session_storage/__init__.py | 6 +-
.../{session_storage_mixin.py => abstract.py} | 89 ++++++++++----
.../session_storage/base_session_storage.py | 60 ----------
.../{json_session_storage.py => json.py} | 65 +++++-----
pyrogram/client/session_storage/memory.py | 85 +++++++++++++
.../{string_session_storage.py => string.py} | 19 +--
pyrogram/session/session.py | 3 +-
11 files changed, 267 insertions(+), 188 deletions(-)
rename pyrogram/client/session_storage/{session_storage_mixin.py => abstract.py} (50%)
delete mode 100644 pyrogram/client/session_storage/base_session_storage.py
rename pyrogram/client/session_storage/{json_session_storage.py => json.py} (58%)
create mode 100644 pyrogram/client/session_storage/memory.py
rename pyrogram/client/session_storage/{string_session_storage.py => string.py} (62%)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 429abab3ed0..42bd73d63ad 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -50,15 +50,15 @@
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.client.methods.password.utils import compute_check
-from pyrogram.client.session_storage import BaseSessionConfig
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher
from .ext import utils, Syncer, BaseClient
from .methods import Methods
-from .session_storage import SessionDoesNotExist
-from .session_storage.json_session_storage import JsonSessionStorage
-from .session_storage.string_session_storage import StringSessionStorage
+from .session_storage import (
+ SessionDoesNotExist, SessionStorage, MemorySessionStorage, JsonSessionStorage,
+ StringSessionStorage
+)
log = logging.getLogger(__name__)
@@ -183,7 +183,7 @@ class Client(Methods, BaseClient):
"""
def __init__(self,
- session_name: Union[str, BaseSessionConfig],
+ session_name: Union[str, SessionStorage],
api_id: Union[int, str] = None,
api_hash: str = None,
app_version: str = None,
@@ -209,14 +209,16 @@ def __init__(self,
takeout: bool = None):
if isinstance(session_name, str):
- if session_name.startswith(':'):
+ if session_name == ':memory:':
+ session_storage = MemorySessionStorage(self)
+ elif session_name.startswith(':'):
session_storage = StringSessionStorage(self, session_name)
else:
session_storage = JsonSessionStorage(self, session_name)
- elif isinstance(session_name, BaseSessionConfig):
- session_storage = session_name.session_storage_cls(self, session_name)
+ elif isinstance(session_name, SessionStorage):
+ session_storage = session_name
else:
- raise RuntimeError('Wrong session_name passed, expected str or BaseSessionConfig subclass')
+ raise RuntimeError('Wrong session_name passed, expected str or SessionConfig subclass')
super().__init__(session_storage)
@@ -230,7 +232,7 @@ def __init__(self,
self.ipv6 = ipv6
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
- self.test_mode = test_mode
+ self.session_storage.test_mode = test_mode
self.phone_number = phone_number
self.phone_code = phone_code
self.password = password
@@ -282,10 +284,10 @@ def start(self):
raise ConnectionError("Client has already been started")
if isinstance(self.session_storage, JsonSessionStorage):
- if self.BOT_TOKEN_RE.match(self.session_storage.session_data):
- self.is_bot = True
- self.bot_token = self.session_storage.session_data
- self.session_storage.session_data = self.session_storage.session_data.split(":")[0]
+ if self.BOT_TOKEN_RE.match(self.session_storage._session_name):
+ self.session_storage.is_bot = True
+ self.bot_token = self.session_storage._session_name
+ self.session_storage._session_name = self.session_storage._session_name.split(":")[0]
warnings.warn('\nYou are using a bot token as session name.\n'
'It will be deprecated in next update, please use session file name to load '
'existing sessions and bot_token argument to create new sessions.',
@@ -297,33 +299,33 @@ def start(self):
self.session = Session(
self,
- self.dc_id,
- self.auth_key
+ self.session_storage.dc_id,
+ self.session_storage.auth_key
)
self.session.start()
self.is_started = True
try:
- if self.user_id is None:
+ if self.session_storage.user_id is None:
if self.bot_token is None:
self.authorize_user()
else:
- self.is_bot = True
+ self.session_storage.is_bot = True
self.authorize_bot()
self.save_session()
- if not self.is_bot:
+ if not self.session_storage.is_bot:
if self.takeout:
self.takeout_id = self.send(functions.account.InitTakeoutSession()).id
log.warning("Takeout session {} initiated".format(self.takeout_id))
now = time.time()
- if abs(now - self.date) > Client.OFFLINE_SLEEP:
- self.peers_by_username.clear()
- self.peers_by_phone.clear()
+ if abs(now - self.session_storage.date) > Client.OFFLINE_SLEEP:
+ self.session_storage.peers_by_username.clear()
+ self.session_storage.peers_by_phone.clear()
self.get_initial_dialogs()
self.get_contacts()
@@ -512,19 +514,20 @@ def authorize_bot(self):
except UserMigrate as e:
self.session.stop()
- self.dc_id = e.x
- self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
+ self.session_storage.dc_id = e.x
+ self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode,
+ self.ipv6, self._proxy).create()
self.session = Session(
self,
- self.dc_id,
- self.auth_key
+ self.session_storage.dc_id,
+ self.session_storage.auth_key
)
self.session.start()
self.authorize_bot()
else:
- self.user_id = r.user.id
+ self.session_storage.user_id = r.user.id
print("Logged in successfully as @{}".format(r.user.username))
@@ -564,19 +567,19 @@ def default_phone_number_callback():
except (PhoneMigrate, NetworkMigrate) as e:
self.session.stop()
- self.dc_id = e.x
+ self.session_storage.dc_id = e.x
- self.auth_key = Auth(
- self.dc_id,
- self.test_mode,
+ self.session_storage.auth_key = Auth(
+ self.session_storage.dc_id,
+ self.session_storage.test_mode,
self.ipv6,
self._proxy
).create()
self.session = Session(
self,
- self.dc_id,
- self.auth_key
+ self.session_storage.dc_id,
+ self.session_storage.auth_key
)
self.session.start()
@@ -752,7 +755,7 @@ def default_recovery_callback(email_pattern: str) -> str:
assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id))
self.password = None
- self.user_id = r.user.id
+ self.session_storage.user_id = r.user.id
print("Logged in successfully as {}".format(r.user.first_name))
@@ -776,13 +779,13 @@ def fetch_peers(self, entities: List[Union[types.User,
access_hash=access_hash
)
- self.peers_by_id[user_id] = input_peer
+ self.session_storage.peers_by_id[user_id] = input_peer
if username is not None:
- self.peers_by_username[username.lower()] = input_peer
+ self.session_storage.peers_by_username[username.lower()] = input_peer
if phone is not None:
- self.peers_by_phone[phone] = input_peer
+ self.session_storage.peers_by_phone[phone] = input_peer
if isinstance(entity, (types.Chat, types.ChatForbidden)):
chat_id = entity.id
@@ -792,7 +795,7 @@ def fetch_peers(self, entities: List[Union[types.User,
chat_id=chat_id
)
- self.peers_by_id[peer_id] = input_peer
+ self.session_storage.peers_by_id[peer_id] = input_peer
if isinstance(entity, (types.Channel, types.ChannelForbidden)):
channel_id = entity.id
@@ -810,10 +813,10 @@ def fetch_peers(self, entities: List[Union[types.User,
access_hash=access_hash
)
- self.peers_by_id[peer_id] = input_peer
+ self.session_storage.peers_by_id[peer_id] = input_peer
if username is not None:
- self.peers_by_username[username.lower()] = input_peer
+ self.session_storage.peers_by_username[username.lower()] = input_peer
def download_worker(self):
name = threading.current_thread().name
@@ -1127,10 +1130,11 @@ def load_config(self):
def load_session(self):
try:
- self.session_storage.load_session()
+ self.session_storage.load()
except SessionDoesNotExist:
log.info('Could not load session "{}", initiate new one'.format(self.session_name))
- self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
+ self.session_storage.auth_key = Auth(self.session_storage.dc_id, self.session_storage.test_mode,
+ self.ipv6, self._proxy).create()
def load_plugins(self):
if self.plugins.get("enabled", False):
@@ -1237,7 +1241,7 @@ def load_plugins(self):
log.warning('No plugin loaded from "{}"'.format(root))
def save_session(self):
- self.session_storage.save_session()
+ self.session_storage.save()
def get_initial_dialogs_chunk(self,
offset_date: int = 0):
@@ -1257,7 +1261,7 @@ def get_initial_dialogs_chunk(self,
log.warning("get_dialogs flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
else:
- log.info("Total peers: {}".format(len(self.peers_by_id)))
+ log.info("Total peers: {}".format(len(self.session_storage.peers_by_id)))
return r
def get_initial_dialogs(self):
@@ -1293,7 +1297,7 @@ def resolve_peer(self,
``KeyError`` in case the peer doesn't exist in the internal database.
"""
try:
- return self.peers_by_id[peer_id]
+ return self.session_storage.peers_by_id[peer_id]
except KeyError:
if type(peer_id) is str:
if peer_id in ("self", "me"):
@@ -1304,17 +1308,17 @@ def resolve_peer(self,
try:
int(peer_id)
except ValueError:
- if peer_id not in self.peers_by_username:
+ if peer_id not in self.session_storage.peers_by_username:
self.send(
functions.contacts.ResolveUsername(
username=peer_id
)
)
- return self.peers_by_username[peer_id]
+ return self.session_storage.peers_by_username[peer_id]
else:
try:
- return self.peers_by_phone[peer_id]
+ return self.session_storage.peers_by_phone[peer_id]
except KeyError:
raise PeerIdInvalid
@@ -1341,7 +1345,7 @@ def resolve_peer(self,
)
try:
- return self.peers_by_id[peer_id]
+ return self.session_storage.peers_by_id[peer_id]
except KeyError:
raise PeerIdInvalid
@@ -1411,7 +1415,7 @@ def save_file(self,
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)
+ session = Session(self, self.session_storage.dc_id, self.session_storage.auth_key, is_media=True)
session.start()
try:
@@ -1492,7 +1496,7 @@ def get_file(self,
session = self.media_sessions.get(dc_id, None)
if session is None:
- if dc_id != self.dc_id:
+ if dc_id != self.session_storage.dc_id:
exported_auth = self.send(
functions.auth.ExportAuthorization(
dc_id=dc_id
@@ -1502,7 +1506,7 @@ def get_file(self,
session = Session(
self,
dc_id,
- Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(),
+ Auth(dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(),
is_media=True
)
@@ -1520,7 +1524,7 @@ def get_file(self,
session = Session(
self,
dc_id,
- self.auth_key,
+ self.session_storage.auth_key,
is_media=True
)
@@ -1588,7 +1592,7 @@ def get_file(self,
cdn_session = Session(
self,
r.dc_id,
- Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(),
+ Auth(r.dc_id, self.session_storage.test_mode, self.ipv6, self._proxy).create(),
is_media=True,
is_cdn=True
)
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index 3f40865ff4d..732a600fa34 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -24,10 +24,10 @@
from pyrogram import __version__
from ..style import Markdown, HTML
from ...session.internals import MsgId
-from ..session_storage import SessionStorageMixin, BaseSessionStorage
+from ..session_storage import SessionStorage
-class BaseClient(SessionStorageMixin):
+class BaseClient:
class StopTransmission(StopIteration):
pass
@@ -68,14 +68,14 @@ class StopTransmission(StopIteration):
13: "video_note"
}
- def __init__(self, session_storage: BaseSessionStorage):
+ def __init__(self, session_storage: SessionStorage):
self.session_storage = session_storage
self.rnd_id = MsgId
self.channels_pts = {}
- self.markdown = Markdown(self.peers_by_id)
- self.html = HTML(self.peers_by_id)
+ self.markdown = Markdown(self.session_storage.peers_by_id)
+ self.html = HTML(self.session_storage.peers_by_id)
self.session = None
self.media_sessions = {}
diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py
index 709556245df..e13212bec62 100644
--- a/pyrogram/client/ext/syncer.py
+++ b/pyrogram/client/ext/syncer.py
@@ -81,9 +81,9 @@ def worker(cls):
@classmethod
def sync(cls, client):
- client.date = int(time.time())
+ client.session_storage.date = int(time.time())
try:
- client.session_storage.save_session(sync=True)
+ client.session_storage.save(sync=True)
except Exception as e:
log.critical(e, exc_info=True)
else:
diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py
index 29b7e176d5d..35b24592504 100644
--- a/pyrogram/client/methods/contacts/get_contacts.py
+++ b/pyrogram/client/methods/contacts/get_contacts.py
@@ -44,5 +44,5 @@ def get_contacts(self):
log.warning("get_contacts flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
else:
- log.info("Total contacts: {}".format(len(self.peers_by_phone)))
+ log.info("Total contacts: {}".format(len(self.session_storage.peers_by_phone)))
return [pyrogram.User._parse(self, user) for user in contacts.users]
diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py
index 611ec9b7441..ad2d8900548 100644
--- a/pyrogram/client/session_storage/__init__.py
+++ b/pyrogram/client/session_storage/__init__.py
@@ -16,5 +16,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from .session_storage_mixin import SessionStorageMixin
-from .base_session_storage import BaseSessionStorage, BaseSessionConfig, SessionDoesNotExist
+from .abstract import SessionStorage, SessionDoesNotExist
+from .memory import MemorySessionStorage
+from .json import JsonSessionStorage
+from .string import StringSessionStorage
diff --git a/pyrogram/client/session_storage/session_storage_mixin.py b/pyrogram/client/session_storage/abstract.py
similarity index 50%
rename from pyrogram/client/session_storage/session_storage_mixin.py
rename to pyrogram/client/session_storage/abstract.py
index 7d783ca72fb..e8f4441e664 100644
--- a/pyrogram/client/session_storage/session_storage_mixin.py
+++ b/pyrogram/client/session_storage/abstract.py
@@ -16,66 +16,103 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see .
-from typing import Dict
+import abc
+from typing import Type
+import pyrogram
-class SessionStorageMixin:
+
+class SessionDoesNotExist(Exception):
+ pass
+
+
+class SessionStorage(abc.ABC):
+ def __init__(self, client: 'pyrogram.client.BaseClient'):
+ self._client = client
+
+ @abc.abstractmethod
+ def load(self):
+ ...
+
+ @abc.abstractmethod
+ def save(self, sync=False):
+ ...
+
+ @abc.abstractmethod
+ def sync_cleanup(self):
+ ...
+
@property
- def dc_id(self) -> int:
- return self.session_storage.dc_id
+ @abc.abstractmethod
+ def dc_id(self):
+ ...
@dc_id.setter
+ @abc.abstractmethod
def dc_id(self, val):
- self.session_storage.dc_id = val
+ ...
@property
- def test_mode(self) -> bool:
- return self.session_storage.test_mode
+ @abc.abstractmethod
+ def test_mode(self):
+ ...
@test_mode.setter
+ @abc.abstractmethod
def test_mode(self, val):
- self.session_storage.test_mode = val
+ ...
@property
- def auth_key(self) -> bytes:
- return self.session_storage.auth_key
+ @abc.abstractmethod
+ def auth_key(self):
+ ...
@auth_key.setter
+ @abc.abstractmethod
def auth_key(self, val):
- self.session_storage.auth_key = val
+ ...
@property
+ @abc.abstractmethod
def user_id(self):
- return self.session_storage.user_id
+ ...
@user_id.setter
- def user_id(self, val) -> int:
- self.session_storage.user_id = val
+ @abc.abstractmethod
+ def user_id(self, val):
+ ...
@property
- def date(self) -> int:
- return self.session_storage.date
+ @abc.abstractmethod
+ def date(self):
+ ...
@date.setter
+ @abc.abstractmethod
def date(self, val):
- self.session_storage.date = val
+ ...
@property
+ @abc.abstractmethod
def is_bot(self):
- return self.session_storage.is_bot
+ ...
@is_bot.setter
- def is_bot(self, val) -> int:
- self.session_storage.is_bot = val
+ @abc.abstractmethod
+ def is_bot(self, val):
+ ...
@property
- def peers_by_id(self) -> Dict[str, int]:
- return self.session_storage.peers_by_id
+ @abc.abstractmethod
+ def peers_by_id(self):
+ ...
@property
- def peers_by_username(self) -> Dict[str, int]:
- return self.session_storage.peers_by_username
+ @abc.abstractmethod
+ def peers_by_username(self):
+ ...
@property
- def peers_by_phone(self) -> Dict[str, int]:
- return self.session_storage.peers_by_phone
+ @abc.abstractmethod
+ def peers_by_phone(self):
+ ...
diff --git a/pyrogram/client/session_storage/base_session_storage.py b/pyrogram/client/session_storage/base_session_storage.py
deleted file mode 100644
index 92473956c5e..00000000000
--- a/pyrogram/client/session_storage/base_session_storage.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-2019 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 .
-
-import abc
-from typing import Type
-
-import pyrogram
-
-
-class SessionDoesNotExist(Exception):
- pass
-
-
-class BaseSessionStorage(abc.ABC):
- def __init__(self, client: 'pyrogram.client.BaseClient', session_data):
- self.client = client
- self.session_data = session_data
- self.dc_id = 1
- self.test_mode = None
- self.auth_key = None
- self.user_id = None
- self.date = 0
- self.is_bot = False
- self.peers_by_id = {}
- self.peers_by_username = {}
- self.peers_by_phone = {}
-
- @abc.abstractmethod
- def load_session(self):
- ...
-
- @abc.abstractmethod
- def save_session(self, sync=False):
- ...
-
- @abc.abstractmethod
- def sync_cleanup(self):
- ...
-
-
-class BaseSessionConfig(abc.ABC):
- @property
- @abc.abstractmethod
- def session_storage_cls(self) -> Type[BaseSessionStorage]:
- ...
diff --git a/pyrogram/client/session_storage/json_session_storage.py b/pyrogram/client/session_storage/json.py
similarity index 58%
rename from pyrogram/client/session_storage/json_session_storage.py
rename to pyrogram/client/session_storage/json.py
index 1e1e0ca4a07..170089a4b0c 100644
--- a/pyrogram/client/session_storage/json_session_storage.py
+++ b/pyrogram/client/session_storage/json.py
@@ -22,21 +22,26 @@
import os
import shutil
+import pyrogram
from ..ext import utils
-from . import BaseSessionStorage, SessionDoesNotExist
+from . import MemorySessionStorage, SessionDoesNotExist
log = logging.getLogger(__name__)
-class JsonSessionStorage(BaseSessionStorage):
+class JsonSessionStorage(MemorySessionStorage):
+ def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str):
+ super(JsonSessionStorage, self).__init__(client)
+ self._session_name = session_name
+
def _get_file_name(self, name: str):
if not name.endswith('.session'):
name += '.session'
- return os.path.join(self.client.workdir, name)
+ return os.path.join(self._client.workdir, name)
- def load_session(self):
- file_path = self._get_file_name(self.session_data)
+ def load(self):
+ file_path = self._get_file_name(self._session_name)
log.info('Loading JSON session from {}'.format(file_path))
try:
@@ -45,59 +50,59 @@ def load_session(self):
except FileNotFoundError:
raise SessionDoesNotExist()
- self.dc_id = s["dc_id"]
- self.test_mode = s["test_mode"]
- self.auth_key = base64.b64decode("".join(s["auth_key"])) # join split key
- self.user_id = s["user_id"]
- self.date = s.get("date", 0)
- self.is_bot = s.get('is_bot', self.client.is_bot)
+ self._dc_id = s["dc_id"]
+ self._test_mode = s["test_mode"]
+ self._auth_key = base64.b64decode("".join(s["auth_key"])) # join split key
+ self._user_id = s["user_id"]
+ self._date = s.get("date", 0)
+ self._is_bot = s.get('is_bot', self._is_bot)
for k, v in s.get("peers_by_id", {}).items():
- self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
+ self._peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
for k, v in s.get("peers_by_username", {}).items():
- peer = self.peers_by_id.get(v, None)
+ peer = self._peers_by_id.get(v, None)
if peer:
- self.peers_by_username[k] = peer
+ self._peers_by_username[k] = peer
for k, v in s.get("peers_by_phone", {}).items():
- peer = self.peers_by_id.get(v, None)
+ peer = self._peers_by_id.get(v, None)
if peer:
- self.peers_by_phone[k] = peer
+ self._peers_by_phone[k] = peer
- def save_session(self, sync=False):
- file_path = self._get_file_name(self.session_data)
+ def save(self, sync=False):
+ file_path = self._get_file_name(self._session_name)
if sync:
file_path += '.tmp'
log.info('Saving JSON session to {}, sync={}'.format(file_path, sync))
- auth_key = base64.b64encode(self.auth_key).decode()
+ auth_key = base64.b64encode(self._auth_key).decode()
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars
- os.makedirs(self.client.workdir, exist_ok=True)
+ os.makedirs(self._client.workdir, exist_ok=True)
data = {
- 'dc_id': self.dc_id,
- 'test_mode': self.test_mode,
+ 'dc_id': self._dc_id,
+ 'test_mode': self._test_mode,
'auth_key': auth_key,
- 'user_id': self.user_id,
- 'date': self.date,
- 'is_bot': self.is_bot,
+ 'user_id': self._user_id,
+ 'date': self._date,
+ 'is_bot': self._is_bot,
'peers_by_id': {
k: getattr(v, "access_hash", None)
- for k, v in self.peers_by_id.copy().items()
+ for k, v in self._peers_by_id.copy().items()
},
'peers_by_username': {
k: utils.get_peer_id(v)
- for k, v in self.peers_by_username.copy().items()
+ for k, v in self._peers_by_username.copy().items()
},
'peers_by_phone': {
k: utils.get_peer_id(v)
- for k, v in self.peers_by_phone.copy().items()
+ for k, v in self._peers_by_phone.copy().items()
}
}
@@ -109,10 +114,10 @@ def save_session(self, sync=False):
# execution won't be here if an error has occurred earlier
if sync:
- shutil.move(file_path, self._get_file_name(self.session_data))
+ shutil.move(file_path, self._get_file_name(self._session_name))
def sync_cleanup(self):
try:
- os.remove(self._get_file_name(self.session_data) + '.tmp')
+ os.remove(self._get_file_name(self._session_name) + '.tmp')
except OSError:
pass
diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py
new file mode 100644
index 00000000000..f456f8eb150
--- /dev/null
+++ b/pyrogram/client/session_storage/memory.py
@@ -0,0 +1,85 @@
+import pyrogram
+from . import SessionStorage, SessionDoesNotExist
+
+
+class MemorySessionStorage(SessionStorage):
+ def __init__(self, client: 'pyrogram.client.ext.BaseClient'):
+ super(MemorySessionStorage, self).__init__(client)
+ self._dc_id = 1
+ self._test_mode = None
+ self._auth_key = None
+ self._user_id = None
+ self._date = 0
+ self._is_bot = False
+ self._peers_by_id = {}
+ self._peers_by_username = {}
+ self._peers_by_phone = {}
+
+ def load(self):
+ raise SessionDoesNotExist()
+
+ def save(self, sync=False):
+ pass
+
+ def sync_cleanup(self):
+ pass
+
+ @property
+ def dc_id(self):
+ return self._dc_id
+
+ @dc_id.setter
+ def dc_id(self, val):
+ self._dc_id = val
+
+ @property
+ def test_mode(self):
+ return self._test_mode
+
+ @test_mode.setter
+ def test_mode(self, val):
+ self._test_mode = val
+
+ @property
+ def auth_key(self):
+ return self._auth_key
+
+ @auth_key.setter
+ def auth_key(self, val):
+ self._auth_key = val
+
+ @property
+ def user_id(self):
+ return self._user_id
+
+ @user_id.setter
+ def user_id(self, val):
+ self._user_id = val
+
+ @property
+ def date(self):
+ return self._date
+
+ @date.setter
+ def date(self, val):
+ self._date = val
+
+ @property
+ def is_bot(self):
+ return self._is_bot
+
+ @is_bot.setter
+ def is_bot(self, val):
+ self._is_bot = val
+
+ @property
+ def peers_by_id(self):
+ return self._peers_by_id
+
+ @property
+ def peers_by_username(self):
+ return self._peers_by_username
+
+ @property
+ def peers_by_phone(self):
+ return self._peers_by_phone
diff --git a/pyrogram/client/session_storage/string_session_storage.py b/pyrogram/client/session_storage/string.py
similarity index 62%
rename from pyrogram/client/session_storage/string_session_storage.py
rename to pyrogram/client/session_storage/string.py
index 5b1a8cc1ae8..f8ec740a68e 100644
--- a/pyrogram/client/session_storage/string_session_storage.py
+++ b/pyrogram/client/session_storage/string.py
@@ -2,10 +2,11 @@
import binascii
import struct
-from . import BaseSessionStorage, SessionDoesNotExist
+import pyrogram
+from . import MemorySessionStorage, SessionDoesNotExist
-class StringSessionStorage(BaseSessionStorage):
+class StringSessionStorage(MemorySessionStorage):
"""
Packs session data as following (forcing little-endian byte order):
Char dc_id (1 byte, unsigned)
@@ -18,22 +19,26 @@ class StringSessionStorage(BaseSessionStorage):
"""
PACK_FORMAT = '
Date: Sat, 23 Feb 2019 12:09:27 +0100
Subject: [PATCH 0108/1652] 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 2ec22f5a6c1..37307111e10 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 260043d8ec6d32747934e42ee8df115220629f83 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Tue, 26 Feb 2019 19:24:00 +0300
Subject: [PATCH 0109/1652] Unify peers cache
---
pyrogram/client/client.py | 72 +++----------------
pyrogram/client/ext/base_client.py | 4 +-
.../client/methods/contacts/get_contacts.py | 2 +-
pyrogram/client/session_storage/abstract.py | 39 ++++++++--
pyrogram/client/session_storage/json.py | 33 +++++----
pyrogram/client/session_storage/memory.py | 61 ++++++++++++----
pyrogram/client/style/html.py | 10 ++-
pyrogram/client/style/markdown.py | 10 ++-
8 files changed, 124 insertions(+), 107 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 42bd73d63ad..ad755977241 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -324,8 +324,7 @@ def start(self):
now = time.time()
if abs(now - self.session_storage.date) > Client.OFFLINE_SLEEP:
- self.session_storage.peers_by_username.clear()
- self.session_storage.peers_by_phone.clear()
+ self.session_storage.clear_cache()
self.get_initial_dialogs()
self.get_contacts()
@@ -763,60 +762,7 @@ def fetch_peers(self, entities: List[Union[types.User,
types.Chat, types.ChatForbidden,
types.Channel, types.ChannelForbidden]]):
for entity in entities:
- if isinstance(entity, types.User):
- user_id = entity.id
-
- access_hash = entity.access_hash
-
- if access_hash is None:
- continue
-
- username = entity.username
- phone = entity.phone
-
- input_peer = types.InputPeerUser(
- user_id=user_id,
- access_hash=access_hash
- )
-
- self.session_storage.peers_by_id[user_id] = input_peer
-
- if username is not None:
- self.session_storage.peers_by_username[username.lower()] = input_peer
-
- if phone is not None:
- self.session_storage.peers_by_phone[phone] = input_peer
-
- if isinstance(entity, (types.Chat, types.ChatForbidden)):
- chat_id = entity.id
- peer_id = -chat_id
-
- input_peer = types.InputPeerChat(
- chat_id=chat_id
- )
-
- self.session_storage.peers_by_id[peer_id] = input_peer
-
- if isinstance(entity, (types.Channel, types.ChannelForbidden)):
- channel_id = entity.id
- peer_id = int("-100" + str(channel_id))
-
- access_hash = entity.access_hash
-
- if access_hash is None:
- continue
-
- username = getattr(entity, "username", None)
-
- input_peer = types.InputPeerChannel(
- channel_id=channel_id,
- access_hash=access_hash
- )
-
- self.session_storage.peers_by_id[peer_id] = input_peer
-
- if username is not None:
- self.session_storage.peers_by_username[username.lower()] = input_peer
+ self.session_storage.cache_peer(entity)
def download_worker(self):
name = threading.current_thread().name
@@ -1261,7 +1207,7 @@ def get_initial_dialogs_chunk(self,
log.warning("get_dialogs flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
else:
- log.info("Total peers: {}".format(len(self.session_storage.peers_by_id)))
+ log.info("Total peers: {}".format(self.session_storage.peers_count()))
return r
def get_initial_dialogs(self):
@@ -1297,7 +1243,7 @@ def resolve_peer(self,
``KeyError`` in case the peer doesn't exist in the internal database.
"""
try:
- return self.session_storage.peers_by_id[peer_id]
+ return self.session_storage.get_peer_by_id(peer_id)
except KeyError:
if type(peer_id) is str:
if peer_id in ("self", "me"):
@@ -1308,17 +1254,19 @@ def resolve_peer(self,
try:
int(peer_id)
except ValueError:
- if peer_id not in self.session_storage.peers_by_username:
+ try:
+ self.session_storage.get_peer_by_username(peer_id)
+ except KeyError:
self.send(
functions.contacts.ResolveUsername(
username=peer_id
)
)
- return self.session_storage.peers_by_username[peer_id]
+ return self.session_storage.get_peer_by_username(peer_id)
else:
try:
- return self.session_storage.peers_by_phone[peer_id]
+ return self.session_storage.get_peer_by_phone(peer_id)
except KeyError:
raise PeerIdInvalid
@@ -1345,7 +1293,7 @@ def resolve_peer(self,
)
try:
- return self.session_storage.peers_by_id[peer_id]
+ return self.session_storage.get_peer_by_id(peer_id)
except KeyError:
raise PeerIdInvalid
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index 732a600fa34..1ec65c93d22 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -74,8 +74,8 @@ def __init__(self, session_storage: SessionStorage):
self.rnd_id = MsgId
self.channels_pts = {}
- self.markdown = Markdown(self.session_storage.peers_by_id)
- self.html = HTML(self.session_storage.peers_by_id)
+ self.markdown = Markdown(self.session_storage)
+ self.html = HTML(self.session_storage)
self.session = None
self.media_sessions = {}
diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py
index 35b24592504..1241910631d 100644
--- a/pyrogram/client/methods/contacts/get_contacts.py
+++ b/pyrogram/client/methods/contacts/get_contacts.py
@@ -44,5 +44,5 @@ def get_contacts(self):
log.warning("get_contacts flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
else:
- log.info("Total contacts: {}".format(len(self.session_storage.peers_by_phone)))
+ log.info("Total contacts: {}".format(self.session_storage.contacts_count()))
return [pyrogram.User._parse(self, user) for user in contacts.users]
diff --git a/pyrogram/client/session_storage/abstract.py b/pyrogram/client/session_storage/abstract.py
index e8f4441e664..39517a018fe 100644
--- a/pyrogram/client/session_storage/abstract.py
+++ b/pyrogram/client/session_storage/abstract.py
@@ -17,9 +17,10 @@
# along with Pyrogram. If not, see .
import abc
-from typing import Type
+from typing import Type, Union
import pyrogram
+from pyrogram.api import types
class SessionDoesNotExist(Exception):
@@ -102,17 +103,41 @@ def is_bot(self):
def is_bot(self, val):
...
- @property
@abc.abstractmethod
- def peers_by_id(self):
+ def clear_cache(self):
...
- @property
@abc.abstractmethod
- def peers_by_username(self):
+ def cache_peer(self, entity: Union[types.User,
+ types.Chat, types.ChatForbidden,
+ types.Channel, types.ChannelForbidden]):
+ ...
+
+ @abc.abstractmethod
+ def get_peer_by_id(self, val: int):
+ ...
+
+ @abc.abstractmethod
+ def get_peer_by_username(self, val: str):
+ ...
+
+ @abc.abstractmethod
+ def get_peer_by_phone(self, val: str):
+ ...
+
+ def get_peer(self, peer_id: Union[int, str]):
+ if isinstance(peer_id, int):
+ return self.get_peer_by_id(peer_id)
+ else:
+ peer_id = peer_id.lstrip('+@')
+ if peer_id.isdigit():
+ return self.get_peer_by_phone(peer_id)
+ return self.get_peer_by_username(peer_id)
+
+ @abc.abstractmethod
+ def peers_count(self):
...
- @property
@abc.abstractmethod
- def peers_by_phone(self):
+ def contacts_count(self):
...
diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py
index 170089a4b0c..aaa6b96ff23 100644
--- a/pyrogram/client/session_storage/json.py
+++ b/pyrogram/client/session_storage/json.py
@@ -58,19 +58,19 @@ def load(self):
self._is_bot = s.get('is_bot', self._is_bot)
for k, v in s.get("peers_by_id", {}).items():
- self._peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
+ self._peers_cache['i' + k] = utils.get_input_peer(int(k), v)
for k, v in s.get("peers_by_username", {}).items():
- peer = self._peers_by_id.get(v, None)
-
- if peer:
- self._peers_by_username[k] = peer
+ try:
+ self._peers_cache['u' + k] = self.get_peer_by_id(v)
+ except KeyError:
+ pass
for k, v in s.get("peers_by_phone", {}).items():
- peer = self._peers_by_id.get(v, None)
-
- if peer:
- self._peers_by_phone[k] = peer
+ try:
+ self._peers_cache['p' + k] = self.get_peer_by_id(v)
+ except KeyError:
+ pass
def save(self, sync=False):
file_path = self._get_file_name(self._session_name)
@@ -93,16 +93,19 @@ def save(self, sync=False):
'date': self._date,
'is_bot': self._is_bot,
'peers_by_id': {
- k: getattr(v, "access_hash", None)
- for k, v in self._peers_by_id.copy().items()
+ k[1:]: getattr(v, "access_hash", None)
+ for k, v in self._peers_cache.copy().items()
+ if k[0] == 'i'
},
'peers_by_username': {
- k: utils.get_peer_id(v)
- for k, v in self._peers_by_username.copy().items()
+ k[1:]: utils.get_peer_id(v)
+ for k, v in self._peers_cache.copy().items()
+ if k[0] == 'u'
},
'peers_by_phone': {
- k: utils.get_peer_id(v)
- for k, v in self._peers_by_phone.copy().items()
+ k[1:]: utils.get_peer_id(v)
+ for k, v in self._peers_cache.copy().items()
+ if k[0] == 'p'
}
}
diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py
index f456f8eb150..d5f92f0dba6 100644
--- a/pyrogram/client/session_storage/memory.py
+++ b/pyrogram/client/session_storage/memory.py
@@ -1,4 +1,5 @@
import pyrogram
+from pyrogram.api import types
from . import SessionStorage, SessionDoesNotExist
@@ -11,9 +12,7 @@ def __init__(self, client: 'pyrogram.client.ext.BaseClient'):
self._user_id = None
self._date = 0
self._is_bot = False
- self._peers_by_id = {}
- self._peers_by_username = {}
- self._peers_by_phone = {}
+ self._peers_cache = {}
def load(self):
raise SessionDoesNotExist()
@@ -72,14 +71,48 @@ def is_bot(self):
def is_bot(self, val):
self._is_bot = val
- @property
- def peers_by_id(self):
- return self._peers_by_id
-
- @property
- def peers_by_username(self):
- return self._peers_by_username
-
- @property
- def peers_by_phone(self):
- return self._peers_by_phone
+ def clear_cache(self):
+ keys = list(filter(lambda k: k[0] in 'up', self._peers_cache.keys()))
+ for key in keys:
+ try:
+ del self._peers_cache[key]
+ except KeyError:
+ pass
+
+ def cache_peer(self, entity):
+ if isinstance(entity, types.User):
+ input_peer = types.InputPeerUser(
+ user_id=entity.id,
+ access_hash=entity.access_hash
+ )
+ self._peers_cache['i' + str(entity.id)] = input_peer
+ if entity.username:
+ self._peers_cache['u' + entity.username.lower()] = input_peer
+ if entity.phone:
+ self._peers_cache['p' + entity.phone] = input_peer
+ elif isinstance(entity, (types.Chat, types.ChatForbidden)):
+ self._peers_cache['i-' + str(entity.id)] = types.InputPeerChat(chat_id=entity.id)
+ elif isinstance(entity, (types.Channel, types.ChannelForbidden)):
+ input_peer = types.InputPeerChannel(
+ channel_id=entity.id,
+ access_hash=entity.access_hash
+ )
+ self._peers_cache['i-100' + str(entity.id)] = input_peer
+ username = getattr(entity, "username", None)
+ if username:
+ self._peers_cache['u' + username.lower()] = input_peer
+
+ def get_peer_by_id(self, val):
+ return self._peers_cache['i' + str(val)]
+
+ def get_peer_by_username(self, val):
+ return self._peers_cache['u' + val.lower()]
+
+ def get_peer_by_phone(self, val):
+ return self._peers_cache['p' + val]
+
+ def peers_count(self):
+ return len(list(filter(lambda k: k[0] == 'i', self._peers_cache.keys())))
+
+ def contacts_count(self):
+ return len(list(filter(lambda k: k[0] == 'p', self._peers_cache.keys())))
diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py
index 9a72a56523b..88e317cdd46 100644
--- a/pyrogram/client/style/html.py
+++ b/pyrogram/client/style/html.py
@@ -29,14 +29,15 @@
InputMessageEntityMentionName as Mention,
)
from . import utils
+from ..session_storage import SessionStorage
class HTML:
HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)\1>")
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
- def __init__(self, peers_by_id):
- self.peers_by_id = peers_by_id
+ def __init__(self, session_storage: SessionStorage):
+ self.session_storage = session_storage
def parse(self, message: str):
entities = []
@@ -52,7 +53,10 @@ def parse(self, message: str):
if mention:
user_id = int(mention.group(1))
- input_user = self.peers_by_id.get(user_id, None)
+ try:
+ input_user = self.session_storage.get_peer_by_id(user_id)
+ except KeyError:
+ input_user = None
entity = (
Mention(start, len(body), input_user)
diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py
index 05a11a25c12..6793b6433a4 100644
--- a/pyrogram/client/style/markdown.py
+++ b/pyrogram/client/style/markdown.py
@@ -29,6 +29,7 @@
InputMessageEntityMentionName as Mention
)
from . import utils
+from ..session_storage import SessionStorage
class Markdown:
@@ -52,8 +53,8 @@ class Markdown:
))
MENTION_RE = re.compile(r"tg://user\?id=(\d+)")
- def __init__(self, peers_by_id: dict):
- self.peers_by_id = peers_by_id
+ def __init__(self, session_storage: SessionStorage):
+ self.session_storage = session_storage
def parse(self, message: str):
message = utils.add_surrogates(str(message)).strip()
@@ -69,7 +70,10 @@ def parse(self, message: str):
if mention:
user_id = int(mention.group(1))
- input_user = self.peers_by_id.get(user_id, None)
+ try:
+ input_user = self.session_storage.get_peer_by_id(user_id)
+ except KeyError:
+ input_user = None
entity = (
Mention(start, len(text), input_user)
From 03b92b3302a9d316d4e693efa8b9d87b0b991fd0 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Tue, 26 Feb 2019 21:06:30 +0300
Subject: [PATCH 0110/1652] Implement SQLite session storage
---
pyrogram/client/client.py | 2 +-
pyrogram/client/session_storage/__init__.py | 1 +
pyrogram/client/session_storage/json.py | 6 +-
.../client/session_storage/sqlite/0001.sql | 21 +++
.../client/session_storage/sqlite/__init__.py | 132 ++++++++++++++++++
5 files changed, 159 insertions(+), 3 deletions(-)
create mode 100644 pyrogram/client/session_storage/sqlite/0001.sql
create mode 100644 pyrogram/client/session_storage/sqlite/__init__.py
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index ad755977241..5fc805c0433 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -57,7 +57,7 @@
from .methods import Methods
from .session_storage import (
SessionDoesNotExist, SessionStorage, MemorySessionStorage, JsonSessionStorage,
- StringSessionStorage
+ StringSessionStorage, SQLiteSessionStorage
)
log = logging.getLogger(__name__)
diff --git a/pyrogram/client/session_storage/__init__.py b/pyrogram/client/session_storage/__init__.py
index ad2d8900548..adfcf8132f8 100644
--- a/pyrogram/client/session_storage/__init__.py
+++ b/pyrogram/client/session_storage/__init__.py
@@ -20,3 +20,4 @@
from .memory import MemorySessionStorage
from .json import JsonSessionStorage
from .string import StringSessionStorage
+from .sqlite import SQLiteSessionStorage
diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py
index aaa6b96ff23..570e1525d6c 100644
--- a/pyrogram/client/session_storage/json.py
+++ b/pyrogram/client/session_storage/json.py
@@ -29,6 +29,8 @@
log = logging.getLogger(__name__)
+EXTENSION = '.session'
+
class JsonSessionStorage(MemorySessionStorage):
def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str):
@@ -36,8 +38,8 @@ def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str):
self._session_name = session_name
def _get_file_name(self, name: str):
- if not name.endswith('.session'):
- name += '.session'
+ if not name.endswith(EXTENSION):
+ name += EXTENSION
return os.path.join(self._client.workdir, name)
def load(self):
diff --git a/pyrogram/client/session_storage/sqlite/0001.sql b/pyrogram/client/session_storage/sqlite/0001.sql
new file mode 100644
index 00000000000..d81e95540b8
--- /dev/null
+++ b/pyrogram/client/session_storage/sqlite/0001.sql
@@ -0,0 +1,21 @@
+create table sessions (
+ dc_id integer primary key,
+ test_mode integer,
+ auth_key blob,
+ user_id integer,
+ date integer,
+ is_bot integer
+);
+
+create table peers_cache (
+ id integer primary key,
+ hash integer,
+ username text,
+ phone integer
+);
+
+create table migrations (
+ name text primary key
+);
+
+insert into migrations (name) values ('0001');
diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py
new file mode 100644
index 00000000000..75931109439
--- /dev/null
+++ b/pyrogram/client/session_storage/sqlite/__init__.py
@@ -0,0 +1,132 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2019 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 .
+
+import logging
+import os
+import sqlite3
+
+import pyrogram
+from ....api import types
+from ...ext import utils
+from .. import MemorySessionStorage, SessionDoesNotExist
+
+
+log = logging.getLogger(__name__)
+
+EXTENSION = '.session.sqlite3'
+MIGRATIONS = ['0001']
+
+
+class SQLiteSessionStorage(MemorySessionStorage):
+ def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str):
+ super(SQLiteSessionStorage, self).__init__(client)
+ self._session_name = session_name
+ self._conn = None # type: sqlite3.Connection
+
+ def _get_file_name(self, name: str):
+ if not name.endswith(EXTENSION):
+ name += EXTENSION
+ return os.path.join(self._client.workdir, name)
+
+ def _apply_migrations(self, new_db=False):
+ migrations = MIGRATIONS.copy()
+ if not new_db:
+ cursor = self._conn.cursor()
+ cursor.execute('select name from migrations')
+ for row in cursor.fetchone():
+ migrations.remove(row)
+ for name in migrations:
+ with open(os.path.join(os.path.dirname(__file__), '{}.sql'.format(name))) as script:
+ self._conn.executescript(script.read())
+
+ def load(self):
+ file_path = self._get_file_name(self._session_name)
+ log.info('Loading SQLite session from {}'.format(file_path))
+
+ if os.path.isfile(file_path):
+ self._conn = sqlite3.connect(file_path)
+ self._apply_migrations()
+ else:
+ self._conn = sqlite3.connect(file_path)
+ self._apply_migrations(new_db=True)
+
+ cursor = self._conn.cursor()
+ cursor.execute('select dc_id, test_mode, auth_key, user_id, "date", is_bot from sessions')
+ row = cursor.fetchone()
+ if not row:
+ raise SessionDoesNotExist()
+
+ self._dc_id = row[0]
+ self._test_mode = bool(row[1])
+ self._auth_key = row[2]
+ self._user_id = row[3]
+ self._date = row[4]
+ self._is_bot = bool(row[5])
+
+ def cache_peer(self, entity):
+ peer_id = username = phone = access_hash = None
+
+ if isinstance(entity, types.User):
+ peer_id = entity.id
+ username = entity.username.lower() if entity.username else None
+ phone = entity.phone or None
+ access_hash = entity.access_hash
+ elif isinstance(entity, (types.Chat, types.ChatForbidden)):
+ peer_id = -entity.id
+ # input_peer = types.InputPeerChat(chat_id=entity.id)
+ elif isinstance(entity, (types.Channel, types.ChannelForbidden)):
+ peer_id = int('-100' + str(entity.id))
+ username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None
+ access_hash = entity.access_hash
+
+ self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)',
+ (peer_id, access_hash, username, phone))
+
+ def get_peer_by_id(self, val):
+ cursor = self._conn.cursor()
+ cursor.execute('select id, hash from peers_cache where id = ?', (val,))
+ row = cursor.fetchone()
+ if not row:
+ raise KeyError(val)
+ return utils.get_input_peer(row[0], row[1])
+
+ def get_peer_by_username(self, val):
+ cursor = self._conn.cursor()
+ cursor.execute('select id, hash from peers_cache where username = ?', (val,))
+ row = cursor.fetchone()
+ if not row:
+ raise KeyError(val)
+ return utils.get_input_peer(row[0], row[1])
+
+ def get_peer_by_phone(self, val):
+ cursor = self._conn.cursor()
+ cursor.execute('select id, hash from peers_cache where phone = ?', (val,))
+ row = cursor.fetchone()
+ if not row:
+ raise KeyError(val)
+ return utils.get_input_peer(row[0], row[1])
+
+ def save(self, sync=False):
+ log.info('Committing SQLite session')
+ self._conn.execute('delete from sessions')
+ self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)',
+ (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot))
+ self._conn.commit()
+
+ def sync_cleanup(self):
+ pass
From 10fc340efff40dc54e35bc687af5966b3ad077f5 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Tue, 26 Feb 2019 21:43:23 +0300
Subject: [PATCH 0111/1652] Add session migrating from json; add some indexes
to sqlite sessions
---
pyrogram/client/client.py | 2 +-
.../client/session_storage/sqlite/0001.sql | 3 ++
.../client/session_storage/sqlite/__init__.py | 29 +++++++++++++++----
3 files changed, 28 insertions(+), 6 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 5fc805c0433..d2bc3ee4954 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -214,7 +214,7 @@ def __init__(self,
elif session_name.startswith(':'):
session_storage = StringSessionStorage(self, session_name)
else:
- session_storage = JsonSessionStorage(self, session_name)
+ session_storage = SQLiteSessionStorage(self, session_name)
elif isinstance(session_name, SessionStorage):
session_storage = session_name
else:
diff --git a/pyrogram/client/session_storage/sqlite/0001.sql b/pyrogram/client/session_storage/sqlite/0001.sql
index d81e95540b8..c6c51d24072 100644
--- a/pyrogram/client/session_storage/sqlite/0001.sql
+++ b/pyrogram/client/session_storage/sqlite/0001.sql
@@ -18,4 +18,7 @@ create table migrations (
name text primary key
);
+create index username_idx on peers_cache(username);
+create index phone_idx on peers_cache(phone);
+
insert into migrations (name) values ('0001');
diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py
index 75931109439..4fc7ff640d2 100644
--- a/pyrogram/client/session_storage/sqlite/__init__.py
+++ b/pyrogram/client/session_storage/sqlite/__init__.py
@@ -18,17 +18,18 @@
import logging
import os
+import shutil
import sqlite3
import pyrogram
from ....api import types
from ...ext import utils
-from .. import MemorySessionStorage, SessionDoesNotExist
+from .. import MemorySessionStorage, SessionDoesNotExist, JsonSessionStorage
log = logging.getLogger(__name__)
-EXTENSION = '.session.sqlite3'
+EXTENSION = '.session'
MIGRATIONS = ['0001']
@@ -54,13 +55,32 @@ def _apply_migrations(self, new_db=False):
with open(os.path.join(os.path.dirname(__file__), '{}.sql'.format(name))) as script:
self._conn.executescript(script.read())
+ def _migrate_from_json(self):
+ jss = JsonSessionStorage(self._client, self._session_name)
+ jss.load()
+ file_path = self._get_file_name(self._session_name)
+ self._conn = sqlite3.connect(file_path + '.tmp')
+ self._apply_migrations(new_db=True)
+ self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot = \
+ jss.dc_id, jss.test_mode, jss.auth_key, jss.user_id, jss.date, jss.is_bot
+ self.save()
+ self._conn.close()
+ shutil.move(file_path + '.tmp', file_path)
+ log.warning('Session was migrated from JSON, loading...')
+ self.load()
+
def load(self):
file_path = self._get_file_name(self._session_name)
log.info('Loading SQLite session from {}'.format(file_path))
if os.path.isfile(file_path):
- self._conn = sqlite3.connect(file_path)
- self._apply_migrations()
+ try:
+ self._conn = sqlite3.connect(file_path)
+ self._apply_migrations()
+ except sqlite3.DatabaseError:
+ log.warning('Trying to migrate session from JSON...')
+ self._migrate_from_json()
+ return
else:
self._conn = sqlite3.connect(file_path)
self._apply_migrations(new_db=True)
@@ -88,7 +108,6 @@ def cache_peer(self, entity):
access_hash = entity.access_hash
elif isinstance(entity, (types.Chat, types.ChatForbidden)):
peer_id = -entity.id
- # input_peer = types.InputPeerChat(chat_id=entity.id)
elif isinstance(entity, (types.Channel, types.ChannelForbidden)):
peer_id = int('-100' + str(entity.id))
username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None
From 033622cfb85efbd8a09abf8b0949f9ccc0495b90 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Wed, 27 Feb 2019 22:49:23 +0300
Subject: [PATCH 0112/1652] Cleanup json session storage specific code as it is
used only for migrations
---
pyrogram/client/ext/syncer.py | 2 -
pyrogram/client/session_storage/abstract.py | 4 --
pyrogram/client/session_storage/json.py | 67 +------------------
pyrogram/client/session_storage/memory.py | 3 -
.../client/session_storage/sqlite/__init__.py | 3 -
pyrogram/client/session_storage/string.py | 3 -
6 files changed, 1 insertion(+), 81 deletions(-)
diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py
index e13212bec62..9e7d2303092 100644
--- a/pyrogram/client/ext/syncer.py
+++ b/pyrogram/client/ext/syncer.py
@@ -88,5 +88,3 @@ def sync(cls, client):
log.critical(e, exc_info=True)
else:
log.info("Synced {}".format(client.session_name))
- finally:
- client.session_storage.sync_cleanup()
diff --git a/pyrogram/client/session_storage/abstract.py b/pyrogram/client/session_storage/abstract.py
index 39517a018fe..134d5c8c8e2 100644
--- a/pyrogram/client/session_storage/abstract.py
+++ b/pyrogram/client/session_storage/abstract.py
@@ -38,10 +38,6 @@ def load(self):
@abc.abstractmethod
def save(self, sync=False):
...
-
- @abc.abstractmethod
- def sync_cleanup(self):
- ...
@property
@abc.abstractmethod
diff --git a/pyrogram/client/session_storage/json.py b/pyrogram/client/session_storage/json.py
index 570e1525d6c..4a48d3c1adb 100644
--- a/pyrogram/client/session_storage/json.py
+++ b/pyrogram/client/session_storage/json.py
@@ -59,70 +59,5 @@ def load(self):
self._date = s.get("date", 0)
self._is_bot = s.get('is_bot', self._is_bot)
- for k, v in s.get("peers_by_id", {}).items():
- self._peers_cache['i' + k] = utils.get_input_peer(int(k), v)
-
- for k, v in s.get("peers_by_username", {}).items():
- try:
- self._peers_cache['u' + k] = self.get_peer_by_id(v)
- except KeyError:
- pass
-
- for k, v in s.get("peers_by_phone", {}).items():
- try:
- self._peers_cache['p' + k] = self.get_peer_by_id(v)
- except KeyError:
- pass
-
def save(self, sync=False):
- file_path = self._get_file_name(self._session_name)
-
- if sync:
- file_path += '.tmp'
-
- log.info('Saving JSON session to {}, sync={}'.format(file_path, sync))
-
- auth_key = base64.b64encode(self._auth_key).decode()
- auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] # split key in lines of 43 chars
-
- os.makedirs(self._client.workdir, exist_ok=True)
-
- data = {
- 'dc_id': self._dc_id,
- 'test_mode': self._test_mode,
- 'auth_key': auth_key,
- 'user_id': self._user_id,
- 'date': self._date,
- 'is_bot': self._is_bot,
- 'peers_by_id': {
- k[1:]: getattr(v, "access_hash", None)
- for k, v in self._peers_cache.copy().items()
- if k[0] == 'i'
- },
- 'peers_by_username': {
- k[1:]: utils.get_peer_id(v)
- for k, v in self._peers_cache.copy().items()
- if k[0] == 'u'
- },
- 'peers_by_phone': {
- k[1:]: utils.get_peer_id(v)
- for k, v in self._peers_cache.copy().items()
- if k[0] == 'p'
- }
- }
-
- with open(file_path, "w", encoding="utf-8") as f:
- json.dump(data, f, indent=4)
-
- f.flush()
- os.fsync(f.fileno())
-
- # execution won't be here if an error has occurred earlier
- if sync:
- shutil.move(file_path, self._get_file_name(self._session_name))
-
- def sync_cleanup(self):
- try:
- os.remove(self._get_file_name(self._session_name) + '.tmp')
- except OSError:
- pass
+ pass
diff --git a/pyrogram/client/session_storage/memory.py b/pyrogram/client/session_storage/memory.py
index d5f92f0dba6..c0610e70b80 100644
--- a/pyrogram/client/session_storage/memory.py
+++ b/pyrogram/client/session_storage/memory.py
@@ -20,9 +20,6 @@ def load(self):
def save(self, sync=False):
pass
- def sync_cleanup(self):
- pass
-
@property
def dc_id(self):
return self._dc_id
diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py
index 4fc7ff640d2..0308a4dc072 100644
--- a/pyrogram/client/session_storage/sqlite/__init__.py
+++ b/pyrogram/client/session_storage/sqlite/__init__.py
@@ -146,6 +146,3 @@ def save(self, sync=False):
self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)',
(self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot))
self._conn.commit()
-
- def sync_cleanup(self):
- pass
diff --git a/pyrogram/client/session_storage/string.py b/pyrogram/client/session_storage/string.py
index f8ec740a68e..11051323270 100644
--- a/pyrogram/client/session_storage/string.py
+++ b/pyrogram/client/session_storage/string.py
@@ -44,6 +44,3 @@ def save(self, sync=False):
encoded = ':' + base64.b64encode(packed, b'-_').decode('latin-1').rstrip('=')
split = '\n'.join(['"{}"'.format(encoded[i: i + 50]) for i in range(0, len(encoded), 50)])
print('Created session string:\n{}'.format(split))
-
- def sync_cleanup(self):
- pass
From 8cc61f00ed74fc8290b4d75cc1503275a42d5136 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Fri, 1 Mar 2019 21:23:01 +0300
Subject: [PATCH 0113/1652] Fix threading with sqlite storage
---
.../client/session_storage/sqlite/__init__.py | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/pyrogram/client/session_storage/sqlite/__init__.py b/pyrogram/client/session_storage/sqlite/__init__.py
index 0308a4dc072..a16e75e89d2 100644
--- a/pyrogram/client/session_storage/sqlite/__init__.py
+++ b/pyrogram/client/session_storage/sqlite/__init__.py
@@ -20,6 +20,7 @@
import os
import shutil
import sqlite3
+from threading import Lock
import pyrogram
from ....api import types
@@ -38,6 +39,7 @@ def __init__(self, client: 'pyrogram.client.ext.BaseClient', session_name: str):
super(SQLiteSessionStorage, self).__init__(client)
self._session_name = session_name
self._conn = None # type: sqlite3.Connection
+ self._lock = Lock()
def _get_file_name(self, name: str):
if not name.endswith(EXTENSION):
@@ -45,6 +47,7 @@ def _get_file_name(self, name: str):
return os.path.join(self._client.workdir, name)
def _apply_migrations(self, new_db=False):
+ self._conn.execute('PRAGMA read_uncommitted = true')
migrations = MIGRATIONS.copy()
if not new_db:
cursor = self._conn.cursor()
@@ -75,14 +78,14 @@ def load(self):
if os.path.isfile(file_path):
try:
- self._conn = sqlite3.connect(file_path)
+ self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False)
self._apply_migrations()
except sqlite3.DatabaseError:
log.warning('Trying to migrate session from JSON...')
self._migrate_from_json()
return
else:
- self._conn = sqlite3.connect(file_path)
+ self._conn = sqlite3.connect(file_path, isolation_level='EXCLUSIVE', check_same_thread=False)
self._apply_migrations(new_db=True)
cursor = self._conn.cursor()
@@ -113,8 +116,9 @@ def cache_peer(self, entity):
username = entity.username.lower() if hasattr(entity, 'username') and entity.username else None
access_hash = entity.access_hash
- self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)',
- (peer_id, access_hash, username, phone))
+ with self._lock:
+ self._conn.execute('insert or replace into peers_cache values (?, ?, ?, ?)',
+ (peer_id, access_hash, username, phone))
def get_peer_by_id(self, val):
cursor = self._conn.cursor()
@@ -142,7 +146,8 @@ def get_peer_by_phone(self, val):
def save(self, sync=False):
log.info('Committing SQLite session')
- self._conn.execute('delete from sessions')
- self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)',
- (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot))
- self._conn.commit()
+ with self._lock:
+ self._conn.execute('delete from sessions')
+ self._conn.execute('insert into sessions values (?, ?, ?, ?, ?, ?)',
+ (self._dc_id, self._test_mode, self._auth_key, self._user_id, self._date, self._is_bot))
+ self._conn.commit()
From 85700b0ffc191458677b6d29452cff121a5d4d13 Mon Sep 17 00:00:00 2001
From: bakatrouble
Date: Fri, 1 Mar 2019 21:23:53 +0300
Subject: [PATCH 0114/1652] Do not cache entities without access_hash
---
pyrogram/client/client.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 2bcf294f380..33b3f1373a9 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -762,6 +762,8 @@ def fetch_peers(self, entities: List[Union[types.User,
types.Chat, types.ChatForbidden,
types.Channel, types.ChannelForbidden]]):
for entity in entities:
+ if isinstance(entity, (types.User, types.Channel, types.ChannelForbidden)) and not entity.access_hash:
+ continue
self.session_storage.cache_peer(entity)
def download_worker(self):
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 0115/1652] 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 f58d3ceb571..5181b0299d3 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1447,11 +1447,13 @@ async def worker(session):
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 0116/1652] 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 843b7197713..afcde68d8b0 100644
--- a/pyrogram/client/methods/messages/send_cached_media.py
+++ b/pyrogram/client/methods/messages/send_cached_media.py
@@ -27,7 +27,7 @@
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 @@ def send_cached_media(
)
)
- 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 @@ def send_cached_media(
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 0117/1652] 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 5b91e81cf2e..f9e3294fdb3 100644
--- a/pyrogram/client/methods/chats/iter_chat_members.py
+++ b/pyrogram/client/methods/chats/iter_chat_members.py
@@ -86,7 +86,7 @@ async def iter_chat_members(self,
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 0118/1652] 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 8a1dc699fa4..ca65619ccc3 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 0119/1652] 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 bf0401da63d..0327615ac0b 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -243,6 +243,12 @@ def __enter__(self):
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 0120/1652] 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 1e10a7b33af..34328589407 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -129,5 +129,5 @@ async def get_chat_members(self, *args, **kwargs):
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 7b3524b23db..65f2ff3a6db 100644
--- a/pyrogram/client/methods/bots/answer_inline_query.py
+++ b/pyrogram/client/methods/bots/answer_inline_query.py
@@ -24,7 +24,7 @@
class AnswerInlineQuery(BaseClient):
- def answer_inline_query(
+ async def answer_inline_query(
self,
inline_query_id: str,
results: List[InlineQueryResult],
@@ -75,7 +75,7 @@ def answer_inline_query(
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 9c1c02aceb8..737960ca722 100644
--- a/pyrogram/client/types/inline_mode/inline_query.py
+++ b/pyrogram/client/types/inline_mode/inline_query.py
@@ -83,7 +83,7 @@ def _parse(client, inline_query: types.UpdateBotInlineQuery, users: dict) -> "In
client=client
)
- def answer(
+ async def answer(
self,
results: List[InlineQueryResult],
cache_time: int = 300,
@@ -141,7 +141,7 @@ def answer(
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 0121/1652] 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 6f654099b9d..bda5f4372af 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1362,7 +1362,7 @@ async def resolve_peer(self,
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 @@ async def resolve_peer(self,
)
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 22e090fc582..8fd89dc6ee4 100644
--- a/pyrogram/client/methods/messages/edit_message_caption.py
+++ b/pyrogram/client/methods/messages/edit_message_caption.py
@@ -67,7 +67,7 @@ async def edit_message_caption(
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 2d9aa23baa9..a5ae56fe1c9 100644
--- a/pyrogram/client/methods/messages/edit_message_media.py
+++ b/pyrogram/client/methods/messages/edit_message_media.py
@@ -353,7 +353,7 @@ async def edit_message_media(
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 b37255a22ad..fac74f89538 100644
--- a/pyrogram/client/methods/messages/edit_message_text.py
+++ b/pyrogram/client/methods/messages/edit_message_text.py
@@ -72,7 +72,7 @@ async def edit_message_text(
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 507cf6f9351..ffe70fd9c43 100644
--- a/pyrogram/client/methods/messages/send_animation.py
+++ b/pyrogram/client/methods/messages/send_animation.py
@@ -187,7 +187,7 @@ async def send_animation(
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 ea3a0ac476a..f620b25b6b4 100644
--- a/pyrogram/client/methods/messages/send_audio.py
+++ b/pyrogram/client/methods/messages/send_audio.py
@@ -186,7 +186,7 @@ async def send_audio(
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 9511c548b6f..b9f004958af 100644
--- a/pyrogram/client/methods/messages/send_cached_media.py
+++ b/pyrogram/client/methods/messages/send_cached_media.py
@@ -122,7 +122,7 @@ async def send_cached_media(
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 123a79fc247..ebdae534718 100644
--- a/pyrogram/client/methods/messages/send_document.py
+++ b/pyrogram/client/methods/messages/send_document.py
@@ -167,7 +167,7 @@ async def send_document(
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 40c53066359..32b6af5934f 100644
--- a/pyrogram/client/methods/messages/send_media_group.py
+++ b/pyrogram/client/methods/messages/send_media_group.py
@@ -183,7 +183,7 @@ async def send_media_group(
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 7c36800e29d..090bd63cb10 100644
--- a/pyrogram/client/methods/messages/send_message.py
+++ b/pyrogram/client/methods/messages/send_message.py
@@ -76,7 +76,7 @@ async def send_message(
: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 5686dfdc50b..0030223368b 100644
--- a/pyrogram/client/methods/messages/send_photo.py
+++ b/pyrogram/client/methods/messages/send_photo.py
@@ -164,7 +164,7 @@ async def send_photo(
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 00a717e467c..a9c72df8584 100644
--- a/pyrogram/client/methods/messages/send_video.py
+++ b/pyrogram/client/methods/messages/send_video.py
@@ -190,7 +190,7 @@ async def send_video(
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 588bb446fb0..3d8bfaf2b02 100644
--- a/pyrogram/client/methods/messages/send_voice.py
+++ b/pyrogram/client/methods/messages/send_voice.py
@@ -166,7 +166,7 @@ async def send_voice(
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 9c0a372c496..d9aec531acf 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 @@ def parse(self, message: str):
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 adb86e94fc1..1174c639c06 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 @@ def parse(self, message: str):
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 0122/1652] 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 7e9f51a1e28..ae05c1f2d79 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -26,7 +26,7 @@
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 0123/1652] 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 d1e195fab61..962b8782bf8 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 0124/1652] 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 8ecb0929048..f1358b86b54 100644
--- a/pyrogram/client/ext/dispatcher.py
+++ b/pyrogram/client/ext/dispatcher.py
@@ -74,7 +74,7 @@ async def user_status_parser(update, users, chats):
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 0125/1652] 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 65f2ff3a6db..88a661d0d9d 100644
--- a/pyrogram/client/methods/bots/answer_inline_query.py
+++ b/pyrogram/client/methods/bots/answer_inline_query.py
@@ -75,10 +75,15 @@ async def answer_inline_query(
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 3e7fcb02b25..6fd1975ddb0 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 @@ def __init__(self, type: str, id: str):
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 8d0089c331b..3f0c299770f 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 @@ def __init__(
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 0e6ffa8b715..dda1f3f3b93 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 @@ def __init__(self, message_text: str, parse_mode: str = "", disable_web_page_pre
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 0126/1652] 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 af02a12dc21..5a155b94afe 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 @@
class SetUserProfilePhoto(BaseClient):
- def set_user_profile_photo(
+ async def set_user_profile_photo(
self,
photo: str
) -> bool:
@@ -43,9 +43,9 @@ def set_user_profile_photo(
"""
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 0127/1652] 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 30a5333fe39..767d768c0b3 100644
--- a/pyrogram/client/types/bots/callback_query.py
+++ b/pyrogram/client/types/bots/callback_query.py
@@ -79,7 +79,7 @@ def __init__(
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 c0c3eb8cbff..5e225846c3d 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 @@ def __init__(
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 0128/1652] 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 39cdfaeb999..12f5fe12ffe 100644
--- a/pyrogram/client/methods/chats/update_chat_username.py
+++ b/pyrogram/client/methods/chats/update_chat_username.py
@@ -23,7 +23,7 @@
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 @@ def update_chat_username(
``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 d0c87eb2f19..15877992b49 100644
--- a/pyrogram/client/methods/users/update_username.py
+++ b/pyrogram/client/methods/users/update_username.py
@@ -23,7 +23,7 @@
class UpdateUsername(BaseClient):
- def update_username(
+ async def update_username(
self,
username: Union[str, None]
) -> bool:
@@ -45,7 +45,7 @@ def update_username(
"""
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 0129/1652] 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 9dc4cf39833..9eb42867c9c 100644
--- a/pyrogram/client/methods/messages/delete_messages.py
+++ b/pyrogram/client/methods/messages/delete_messages.py
@@ -57,14 +57,14 @@ async def delete_messages(
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 0130/1652] 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 b1366b2625a..ce8ae2fd806 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -53,8 +53,8 @@
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 @@ def load_plugins(self):
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 @@ def load_plugins(self):
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 @@ def load_plugins(self):
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 @@ def load_plugins(self):
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 @@ def load_plugins(self):
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 eadda551c6f6c90a89b537c541df27903bcc24b2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 12 May 2019 19:26:55 +0200
Subject: [PATCH 0131/1652] Docs revamp. Part 3
---
compiler/docs/compiler.py | 4 +-
docs/source/api/client.rst | 7 ++
docs/source/{core => api}/decorators.rst | 0
docs/source/{core => api}/errors.rst | 4 +-
docs/source/{core => api}/filters.rst | 4 +-
docs/source/{core => api}/handlers.rst | 0
docs/source/{core => api}/methods.rst | 0
docs/source/{core => api}/types.rst | 0
docs/source/core/client.rst | 12 --
docs/source/index.rst | 118 +++++++++++-------
docs/source/intro/start.rst | 8 +-
.../error-handling.rst => start/errors.rst} | 3 +-
.../{topics/usage.rst => start/invoking.rst} | 14 +--
.../update-handling.rst => start/updates.rst} | 12 +-
.../{auto-authorization.rst => auto-auth.rst} | 0
docs/source/topics/changelog.rst | 11 --
...configuration-file.rst => config-file.rst} | 0
docs/source/topics/faq.rst | 107 ++++++++++++++++
.../topics/{using-filters.rst => filters.rst} | 0
.../topics/{socks5-proxy.rst => proxy.rst} | 0
docs/source/topics/releases.rst | 13 ++
...mize-sessions.rst => session-settings.rst} | 5 +-
docs/source/topics/text-formatting.rst | 6 +-
pyrogram/client/client.py | 4 +-
setup.py | 2 +-
25 files changed, 230 insertions(+), 104 deletions(-)
create mode 100644 docs/source/api/client.rst
rename docs/source/{core => api}/decorators.rst (100%)
rename docs/source/{core => api}/errors.rst (95%)
rename docs/source/{core => api}/filters.rst (61%)
rename docs/source/{core => api}/handlers.rst (100%)
rename docs/source/{core => api}/methods.rst (100%)
rename docs/source/{core => api}/types.rst (100%)
delete mode 100644 docs/source/core/client.rst
rename docs/source/{topics/error-handling.rst => start/errors.rst} (94%)
rename docs/source/{topics/usage.rst => start/invoking.rst} (87%)
rename docs/source/{topics/update-handling.rst => start/updates.rst} (88%)
rename docs/source/topics/{auto-authorization.rst => auto-auth.rst} (100%)
delete mode 100644 docs/source/topics/changelog.rst
rename docs/source/topics/{configuration-file.rst => config-file.rst} (100%)
create mode 100644 docs/source/topics/faq.rst
rename docs/source/topics/{using-filters.rst => filters.rst} (100%)
rename docs/source/topics/{socks5-proxy.rst => proxy.rst} (100%)
create mode 100644 docs/source/topics/releases.rst
rename docs/source/topics/{customize-sessions.rst => session-settings.rst} (97%)
diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 6ea2240d9ea..2c67e66cf11 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -21,7 +21,7 @@
import shutil
HOME = "compiler/docs"
-DESTINATION = "docs/source"
+DESTINATION = "docs/source/telegram"
FUNCTIONS_PATH = "pyrogram/api/functions"
TYPES_PATH = "pyrogram/api/types"
@@ -129,6 +129,6 @@ def start():
FUNCTIONS_PATH = "../../pyrogram/api/functions"
TYPES_PATH = "../../pyrogram/api/types"
HOME = "."
- DESTINATION = "../../docs/source"
+ DESTINATION = "../../docs/source/telegram"
start()
diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst
new file mode 100644
index 00000000000..05c5cd0ce72
--- /dev/null
+++ b/docs/source/api/client.rst
@@ -0,0 +1,7 @@
+Pyrogram Client
+===============
+
+The :class:`Client ` is the main class. It exposes easy-to-use methods that are named
+after the well established Telegram Bot API methods, thus offering a familiar look to Bot developers.
+
+.. autoclass:: pyrogram.Client()
diff --git a/docs/source/core/decorators.rst b/docs/source/api/decorators.rst
similarity index 100%
rename from docs/source/core/decorators.rst
rename to docs/source/api/decorators.rst
diff --git a/docs/source/core/errors.rst b/docs/source/api/errors.rst
similarity index 95%
rename from docs/source/core/errors.rst
rename to docs/source/api/errors.rst
index 68313bddad3..51d1fcd3d00 100644
--- a/docs/source/core/errors.rst
+++ b/docs/source/api/errors.rst
@@ -1,5 +1,5 @@
-Errors
-======
+RPC Errors
+==========
All the Pyrogram errors listed here live inside the ``errors`` sub-package.
diff --git a/docs/source/core/filters.rst b/docs/source/api/filters.rst
similarity index 61%
rename from docs/source/core/filters.rst
rename to docs/source/api/filters.rst
index 091031aef47..87faa801ffa 100644
--- a/docs/source/core/filters.rst
+++ b/docs/source/api/filters.rst
@@ -1,5 +1,5 @@
-Filters
-=======
+Update Filters
+==============
.. autoclass:: pyrogram.Filters
:members:
diff --git a/docs/source/core/handlers.rst b/docs/source/api/handlers.rst
similarity index 100%
rename from docs/source/core/handlers.rst
rename to docs/source/api/handlers.rst
diff --git a/docs/source/core/methods.rst b/docs/source/api/methods.rst
similarity index 100%
rename from docs/source/core/methods.rst
rename to docs/source/api/methods.rst
diff --git a/docs/source/core/types.rst b/docs/source/api/types.rst
similarity index 100%
rename from docs/source/core/types.rst
rename to docs/source/api/types.rst
diff --git a/docs/source/core/client.rst b/docs/source/core/client.rst
deleted file mode 100644
index 43524cb3c8a..00000000000
--- a/docs/source/core/client.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-Pyrogram
-========
-
-In this section you can find a detailed description of the Pyrogram package and its API.
-
-:class:`Client ` is the main class. It exposes easy-to-use methods that are named
-after the well established Telegram Bot API methods, thus offering a familiar look to Bot developers.
-
-Client
-------
-
-.. autoclass:: pyrogram.Client()
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 74f6843330c..ae6a2fdab6b 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -54,26 +54,54 @@ Welcome to Pyrogram
app.run()
-**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
-It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
+**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and
+C. It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the
+`MTProto API`_.
-How the documentation is organized
+.. _Telegram: https://telegram.org
+.. _MTProto API: https://core.telegram.org/api#telegram-api
+
+How the Documentation is Organized
----------------------------------
-Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
-using the Next button at the end of each page.
+Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in
+order using the Next button at the end of each page. Here below you can find a list of the most relevant pages.
+
+Getting Started
+^^^^^^^^^^^^^^^
+
+- `Quick Start`_ - Overview to get you started as fast as possible.
+- `Calling Methods`_ - How to use Pyrogram's API.
+- `Handling Updates`_ - How to handle Telegram updates.
+- `Error Handling`_ - How to handle API errors correctly.
+
+.. _Quick Start: intro/start
+.. _Calling Methods: start/invoking
+.. _Handling Updates: start/updates
+.. _Error Handling: start/errors
+
+API Reference
+^^^^^^^^^^^^^
+- `Client Class`_ - Details about the Client class.
+- `Available Methods`_ - A list of available high-level methods.
+- `Available Types`_ - A list of available high-level types.
-Relevant Pages
-^^^^^^^^^^^^^^
+.. _Client Class: core/client
+.. _Available Methods: core/methods
+.. _Available Types: core/types
-- `Quick Start`_ - Concise steps to get you started as fast as possible.
-- `API Usage`_ - Guide on how to use Pyrogram's API.
-- `Update Handling`_ - Guide on how to handle Telegram updates.
-- Client_ - Reference details about the Client class.
-- Types_ - All the available Pyrogram types.
-- Methods_ - All the available Pyrogram methods.
+Topics
+^^^^^^
-**To get started, press the Next button**
+- `Smart Plugins`_ - How to modularize your application.
+- `Advanced Usage`_ - How to use Telegram's raw API.
+- `Release Notes`_ - Release notes for Pyrogram releases.
+- `Pyrogram FAQ`_ - Answers to common Pyrogram questions.
+
+.. _Smart Plugins: topics/smart-plugins
+.. _Advanced Usage: topics/advanced-usage
+.. _Release Notes: topics/releases
+.. _Pyrogram FAQ: topics/faq
.. toctree::
:hidden:
@@ -82,55 +110,51 @@ Relevant Pages
intro/start
intro/install
intro/setup
+
+.. toctree::
+ :hidden:
+ :caption: Getting Started
+
intro/auth
+ start/invoking
+ start/updates
+ start/errors
+
+.. toctree::
+ :hidden:
+ :caption: API Reference
+
+ api/client
+ api/methods
+ api/types
+ api/handlers
+ api/decorators
+ api/filters
+ api/errors
.. toctree::
:hidden:
:caption: Topic Guides
- topics/usage
- topics/update-handling
- topics/using-filters
+ topics/filters
topics/more-on-updates
- topics/configuration-file
+ topics/config-file
topics/smart-plugins
- topics/auto-authorization
- topics/customize-sessions
+ topics/auto-auth
+ topics/session-settings
topics/tgcrypto
topics/text-formatting
- topics/socks5-proxy
+ topics/proxy
topics/bots-interaction
- topics/error-handling
topics/test-servers
topics/advanced-usage
topics/voice-calls
- topics/changelog
-
-.. toctree::
- :hidden:
- :caption: API Reference
-
- core/client
- core/types
- core/methods
- core/handlers
- core/decorators
- core/filters
- core/errors
+ topics/releases
+ topics/faq
.. toctree::
:hidden:
:caption: Telegram API
- functions/index
- types/index
-
-.. _Telegram: https://telegram.org
-.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
-.. _MTProto API: https://core.telegram.org/api#telegram-api
-.. _Quick Start: intro/start.html
-.. _API Usage: topics/usage.html
-.. _Update Handling: topics/update-handling.html
-.. _Client: core/client.html
-.. _Types: core/types.html
-.. _Methods: core/methods
+ telegram/functions/index
+ telegram/types/index
diff --git a/docs/source/intro/start.rst b/docs/source/intro/start.rst
index d6a8d5e32f8..7d6f0150b5e 100644
--- a/docs/source/intro/start.rst
+++ b/docs/source/intro/start.rst
@@ -32,7 +32,7 @@ Get Pyrogram Real Fast
8. Watch Pyrogram send a message to yourself.
-9. Join our `community /t.me/pyrogramchat>`_.
+9. Join our `community`_.
10. Say, "hi!".
@@ -42,4 +42,8 @@ Enjoy the API
That was just a quick overview that barely scratched the surface!
In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done.
-Feeling eager? You can take a shortcut to `API Usage <../topics/usage.html>`_ and come back later to learn some more details.
+Feeling eager? You can take a shortcut to `Calling Methods`_ and come back later to learn some more
+details.
+
+.. _community: //t.me/pyrogramchat
+.. _Calling Methods: ../start/invoking
\ No newline at end of file
diff --git a/docs/source/topics/error-handling.rst b/docs/source/start/errors.rst
similarity index 94%
rename from docs/source/topics/error-handling.rst
rename to docs/source/start/errors.rst
index 7e87b94ac1e..d05206c91ad 100644
--- a/docs/source/topics/error-handling.rst
+++ b/docs/source/start/errors.rst
@@ -16,8 +16,7 @@ There are many errors that Telegram could return, but they all fall in one of th
As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a
specific one, it raises a special :obj:`520 Unknown Error ` exception and logs it
-in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors; in later versions of Pyrogram
-some kind of automatic error reporting module might be implemented.
+in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors.
Examples
--------
diff --git a/docs/source/topics/usage.rst b/docs/source/start/invoking.rst
similarity index 87%
rename from docs/source/topics/usage.rst
rename to docs/source/start/invoking.rst
index 34f5787650f..c27e86c36fb 100644
--- a/docs/source/topics/usage.rst
+++ b/docs/source/start/invoking.rst
@@ -1,14 +1,10 @@
-API Usage
-=========
+Calling Methods
+===============
-At this point, we have successfully `installed Pyrogram`_ and authorized_ our account and we are now pointing towards
-the core of the library. It's time to start playing with the API!
+At this point, we have successfully `installed Pyrogram`_ and authorized_ our account; we are now pointing towards the
+core of the library. It's time to start playing with the API!
-Make API Method Calls
----------------------
-
-Making API method calls with Pyrogram is very simple.
-Here's an example we are going to examine:
+Making API method calls with Pyrogram is very simple. Here's an example we are going to examine:
.. code-block:: python
diff --git a/docs/source/topics/update-handling.rst b/docs/source/start/updates.rst
similarity index 88%
rename from docs/source/topics/update-handling.rst
rename to docs/source/start/updates.rst
index c32bc2be4e2..0dcb08ad82b 100644
--- a/docs/source/topics/update-handling.rst
+++ b/docs/source/start/updates.rst
@@ -1,12 +1,12 @@
-Update Handling
-===============
+Handling Updates
+================
Calling `API methods`_ sequentially is cool, but how to react when, for example, a new message arrives? This page deals
-with updates and how to handle them in Pyrogram. Let's have a look at how they work.
+with updates and how to handle such events in Pyrogram. Let's have a look at how they work.
-First, let's define what are these updates. Updates are simply events that happen in your Telegram account (incoming
-messages, new members join, button presses, etc...), which are meant to notify you about a new specific state that
-changed. These updates are handled by registering one or more callback functions in your app using
+First, let's define what are these updates. As hinted already, updates are simply events that happen in your Telegram
+account (incoming messages, new members join, button presses, etc...), which are meant to notify you about a new
+specific state that changed. These updates are handled by registering one or more callback functions in your app using
`Handlers <../pyrogram/Handlers.html>`_.
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
diff --git a/docs/source/topics/auto-authorization.rst b/docs/source/topics/auto-auth.rst
similarity index 100%
rename from docs/source/topics/auto-authorization.rst
rename to docs/source/topics/auto-auth.rst
diff --git a/docs/source/topics/changelog.rst b/docs/source/topics/changelog.rst
deleted file mode 100644
index 732a1311ed5..00000000000
--- a/docs/source/topics/changelog.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Changelog
-=========
-
-Currently, all Pyrogram release notes live inside the GitHub repository web page:
-https://github.com/pyrogram/pyrogram/releases
-
-(You will be automatically redirected in 10 seconds.)
-
-.. raw:: html
-
-
\ No newline at end of file
diff --git a/docs/source/topics/configuration-file.rst b/docs/source/topics/config-file.rst
similarity index 100%
rename from docs/source/topics/configuration-file.rst
rename to docs/source/topics/config-file.rst
diff --git a/docs/source/topics/faq.rst b/docs/source/topics/faq.rst
new file mode 100644
index 00000000000..f647e2612b7
--- /dev/null
+++ b/docs/source/topics/faq.rst
@@ -0,0 +1,107 @@
+Pyrogram FAQ
+============
+
+This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general.
+
+.. contents:: Contents
+ :backlinks: none
+ :local:
+
+What is Pyrogram?
+-----------------
+
+**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and
+C. It enables you to easily create custom applications using both user and bot identities (bot API alternative) via the
+`MTProto API`_ with the Python programming language.
+
+.. _Telegram: https://telegram.org
+.. _MTProto API: https://core.telegram.org/api#telegram-api
+
+What does "Pyrogram" mean?
+--------------------------
+
+The word "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**,
+from *Telegram*. The word *pyro* itself is built from *Python*, **py** for short, and the suffix **ro** to come up with
+the word *fire*, which also inspired the project logo.
+
+How old is Pyrogram?
+--------------------
+
+Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior the
+initial public release on `GitHub`_.
+
+.. _GitHub:
+
+Why do I need an API key for bots?
+----------------------------------
+
+Requests against the official bot API endpoint are made via JSON/HTTP, but are handled by a backend application that
+implements the MTProto protocol -- just like Pyrogram -- and uses its own API key, which is always required, but hidden
+to the public.
+
+.. figure:: https://i.imgur.com/C108qkX.png
+ :align: center
+
+Using MTProto is the only way to communicate with the actual Telegram servers, and the main API requires developers to
+identify applications by means of a unique key; the bot token identifies a bot as a user and replaces the user's phone
+number only.
+
+I started a client but nothing happens!
+---------------------------------------
+
+If you are connecting from Russia, China or Iran `you need a proxy`_, because Telegram could be partially or
+totally blocked in those countries.
+
+Another possible cause might be network issues, either yours or Telegram's. To confirm this, add the following code on
+the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable network
+in a bunch of seconds:
+
+.. code-block:: python
+
+ import logging
+ logging.basicConfig(level=logging.INFO)
+
+|bug report|
+
+.. _you need a proxy: proxy
+
+I keep getting [400 PEER_ID_INVALID] error!
+-------------------------------------------
+
+The error in question is ``[400 PEER_ID_INVALID]: The id/access_hash combination is invalid``, and could mean several
+things:
+
+- The chat id you tried to use is simply wrong, double check it.
+- The chat id refers to a group or channel you are not a member of.
+- The chat id refers to a user you have't seen yet (from contacts, groups in common, forwarded messages or private
+ chats).
+
+|bug report|
+
+.. |bug report| replace::
+
+ **Note:** If you really believe this should not happen, kindly open a `Bug Report`_.
+
+.. _Bug Report: https://github.com/pyrogram/pyrogram/issues/new?labels=bug&template=bug_report.md
+
+My account has been deactivated/limited!
+----------------------------------------
+
+First of all, you should understand that Telegram wants to be a safe place for people to stay in, and to pursue this
+goal there are automatic protection systems running to prevent flood and spam, as well as a moderation team of humans
+who reviews reports.
+
+**Pyrogram is a tool at your commands; it only does what you tell it to do, the rest is up to you.**
+
+Having said that, here's how a list of what Telegram definitely doesn't like:
+
+- Flood, abusing the API.
+- Spam, sending unsolicited messages or adding people to unwanted groups and channels.
+- Virtual/VoIP and cheap real numbers, because they are relatively easy to get and likely used for spam/flood.
+
+However, you might be right, and your account was deactivated/limited without any reason. This could happen because of
+mistakes by either the automatic systems or a moderator. In such cases you can kindly email Telegram at
+recover@telegram.org, contact `@smstelegram`_ on Twitter or use `this form`_.
+
+.. _@smstelegram: https://twitter.com/smstelegram
+.. _this form: https://telegram.org/support
\ No newline at end of file
diff --git a/docs/source/topics/using-filters.rst b/docs/source/topics/filters.rst
similarity index 100%
rename from docs/source/topics/using-filters.rst
rename to docs/source/topics/filters.rst
diff --git a/docs/source/topics/socks5-proxy.rst b/docs/source/topics/proxy.rst
similarity index 100%
rename from docs/source/topics/socks5-proxy.rst
rename to docs/source/topics/proxy.rst
diff --git a/docs/source/topics/releases.rst b/docs/source/topics/releases.rst
new file mode 100644
index 00000000000..6c3b5b75539
--- /dev/null
+++ b/docs/source/topics/releases.rst
@@ -0,0 +1,13 @@
+Release Notes
+=============
+
+Release notes for Pyrogram releases will describe what's new in each version, and will also make you aware of any
+backwards-incompatible changes made in that version.
+
+When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find
+incompatible code in your application, but also to take advantage of new features and improvements.
+
+.. note::
+
+ Currently, all Pyrogram release notes live inside the GitHub repository web page:
+ https://github.com/pyrogram/pyrogram/releases.
diff --git a/docs/source/topics/customize-sessions.rst b/docs/source/topics/session-settings.rst
similarity index 97%
rename from docs/source/topics/customize-sessions.rst
rename to docs/source/topics/session-settings.rst
index 77765287ef6..47c6872e9c1 100644
--- a/docs/source/topics/customize-sessions.rst
+++ b/docs/source/topics/session-settings.rst
@@ -1,5 +1,5 @@
-Customize Sessions
-==================
+Session Settings
+================
As you may probably know, Telegram allows users (and bots) having more than one session (authorizations) registered
in the system at the same time.
@@ -8,7 +8,6 @@ Briefly explaining, sessions are simply new logins in your account. They can be
app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ with Pyrogram). They store some
useful information such as the client who's using them and from which country and IP address.
-
.. figure:: https://i.imgur.com/lzGPCdZ.png
:width: 70%
:align: center
diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst
index 0ab086944c1..535fec310ae 100644
--- a/docs/source/topics/text-formatting.rst
+++ b/docs/source/topics/text-formatting.rst
@@ -11,7 +11,7 @@ Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline
Markdown Style
--------------
-To use this mode, pass :obj:`MARKDOWN ` or "markdown" in the *parse_mode* field when using
+To use this mode, pass "markdown" in the *parse_mode* field when using
:obj:`send_message() `. Use the following syntax in your message:
.. code-block:: text
@@ -34,8 +34,8 @@ To use this mode, pass :obj:`MARKDOWN ` or "markdow
HTML Style
----------
-To use this mode, pass :obj:`HTML ` or "html" in the *parse_mode* field when using
-:obj:`send_message() `. The following tags are currently supported:
+To use this mode, pass "html" in the *parse_mode* field when using :obj:`send_message() `.
+The following tags are currently supported:
.. code-block:: text
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 823faf279f5..24373ce1db1 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -60,7 +60,7 @@
class Client(Methods, BaseClient):
- """This class represents a Client, the main mean for interacting with Telegram.
+ """This class represents a Client, the main means for interacting with Telegram.
It exposes bot-like methods for an easy access to the API as well as a simple way to
invoke every single Telegram API method available.
@@ -438,7 +438,7 @@ def signal_handler(*args):
self.stop()
def run(self):
- """Use this method as a convenience shortcut to automatically start the Client and idle the main script.
+ """Use this method to start the Client and automatically idle the main script.
This is a convenience method that literally just calls :meth:`start` and :meth:`idle`. It makes running a client
less verbose, but is not suitable in case you want to run more than one client in a single main script,
diff --git a/setup.py b/setup.py
index ef02c428375..437ba39a49f 100644
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@ def get_readme():
class Clean(Command):
DIST = ["./build", "./dist", "./Pyrogram.egg-info"]
API = ["pyrogram/api/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"]
- DOCS = ["docs/source/functions", "docs/source/types", "docs/build"]
+ DOCS = ["docs/source/telegram", "docs/build"]
ALL = DIST + API + DOCS
description = "Clean generated files"
From ef912d21efd3d44e2a7181fe701aca79bd1b1c70 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 12 May 2019 19:49:06 +0200
Subject: [PATCH 0132/1652] Use more concise and cleaner description of a
method and a type
---
pyrogram/client/client.py | 22 +++++++++----------
pyrogram/client/filters/filters.py | 2 +-
.../methods/bots/answer_callback_query.py | 2 +-
.../methods/bots/answer_inline_query.py | 2 +-
.../methods/bots/get_game_high_scores.py | 2 +-
.../methods/bots/get_inline_bot_results.py | 2 +-
.../methods/bots/request_callback_answer.py | 2 +-
pyrogram/client/methods/bots/send_game.py | 2 +-
.../methods/bots/send_inline_bot_result.py | 2 +-
.../client/methods/bots/set_game_score.py | 2 +-
.../client/methods/chats/delete_chat_photo.py | 2 +-
.../methods/chats/export_chat_invite_link.py | 2 +-
pyrogram/client/methods/chats/get_chat.py | 2 +-
.../client/methods/chats/get_chat_member.py | 2 +-
.../client/methods/chats/get_chat_members.py | 2 +-
.../methods/chats/get_chat_members_count.py | 2 +-
.../client/methods/chats/get_chat_preview.py | 2 +-
pyrogram/client/methods/chats/get_dialogs.py | 2 +-
.../client/methods/chats/get_dialogs_count.py | 2 +-
.../client/methods/chats/iter_chat_members.py | 2 +-
pyrogram/client/methods/chats/iter_dialogs.py | 2 +-
pyrogram/client/methods/chats/join_chat.py | 2 +-
.../client/methods/chats/kick_chat_member.py | 2 +-
pyrogram/client/methods/chats/leave_chat.py | 2 +-
.../client/methods/chats/pin_chat_message.py | 2 +-
.../methods/chats/promote_chat_member.py | 2 +-
.../client/methods/chats/restrict_chat.py | 2 +-
.../methods/chats/restrict_chat_member.py | 2 +-
.../methods/chats/set_chat_description.py | 2 +-
.../client/methods/chats/set_chat_photo.py | 2 +-
.../client/methods/chats/set_chat_title.py | 2 +-
.../client/methods/chats/unban_chat_member.py | 2 +-
.../methods/chats/unpin_chat_message.py | 2 +-
.../methods/chats/update_chat_username.py | 2 +-
.../client/methods/contacts/add_contacts.py | 2 +-
.../methods/contacts/delete_contacts.py | 2 +-
.../client/methods/contacts/get_contacts.py | 2 +-
.../methods/contacts/get_contacts_count.py | 2 +-
.../methods/messages/delete_messages.py | 2 +-
.../client/methods/messages/download_media.py | 2 +-
.../methods/messages/edit_message_caption.py | 2 +-
.../methods/messages/edit_message_media.py | 2 +-
.../messages/edit_message_reply_markup.py | 2 +-
.../methods/messages/edit_message_text.py | 2 +-
.../methods/messages/forward_messages.py | 2 +-
.../client/methods/messages/get_history.py | 2 +-
.../methods/messages/get_history_count.py | 2 +-
.../client/methods/messages/get_messages.py | 2 +-
.../client/methods/messages/iter_history.py | 2 +-
.../client/methods/messages/retract_vote.py | 2 +-
.../client/methods/messages/send_animation.py | 2 +-
.../client/methods/messages/send_audio.py | 2 +-
.../methods/messages/send_cached_media.py | 2 +-
.../methods/messages/send_chat_action.py | 2 +-
.../client/methods/messages/send_contact.py | 2 +-
.../client/methods/messages/send_document.py | 2 +-
.../client/methods/messages/send_location.py | 2 +-
.../methods/messages/send_media_group.py | 2 +-
.../client/methods/messages/send_message.py | 2 +-
.../client/methods/messages/send_photo.py | 2 +-
pyrogram/client/methods/messages/send_poll.py | 2 +-
.../client/methods/messages/send_sticker.py | 2 +-
.../client/methods/messages/send_venue.py | 2 +-
.../client/methods/messages/send_video.py | 2 +-
.../methods/messages/send_video_note.py | 2 +-
.../client/methods/messages/send_voice.py | 2 +-
pyrogram/client/methods/messages/stop_poll.py | 2 +-
pyrogram/client/methods/messages/vote_poll.py | 2 +-
.../methods/password/change_cloud_password.py | 2 +-
.../methods/password/enable_cloud_password.py | 2 +-
.../methods/password/remove_cloud_password.py | 2 +-
.../users/delete_user_profile_photos.py | 2 +-
pyrogram/client/methods/users/get_me.py | 2 +-
.../methods/users/get_user_profile_photos.py | 2 +-
.../users/get_user_profile_photos_count.py | 2 +-
pyrogram/client/methods/users/get_users.py | 2 +-
.../methods/users/set_user_profile_photo.py | 2 +-
.../client/methods/users/update_username.py | 2 +-
.../client/types/inline_mode/inline_query.py | 3 ++-
.../types/inline_mode/inline_query_result.py | 2 +-
.../inline_query_result_article.py | 2 +-
.../client/types/input_media/input_media.py | 4 +++-
.../input_media/input_media_animation.py | 2 +-
.../types/input_media/input_media_audio.py | 3 ++-
.../types/input_media/input_media_document.py | 2 +-
.../types/input_media/input_media_photo.py | 2 +-
.../types/input_media/input_media_video.py | 2 +-
.../types/input_media/input_phone_contact.py | 2 +-
.../input_message_content.py | 2 +-
.../input_text_message_content.py | 2 +-
.../client/types/keyboards/callback_game.py | 2 +-
.../client/types/keyboards/callback_query.py | 3 ++-
.../client/types/keyboards/force_reply.py | 4 +++-
.../client/types/keyboards/game_high_score.py | 2 +-
.../types/keyboards/game_high_scores.py | 2 +-
.../types/keyboards/inline_keyboard_button.py | 4 +++-
.../types/keyboards/inline_keyboard_markup.py | 2 +-
.../client/types/keyboards/keyboard_button.py | 2 +-
.../types/keyboards/reply_keyboard_markup.py | 2 +-
.../types/keyboards/reply_keyboard_remove.py | 9 +++++---
.../types/messages_and_media/animation.py | 2 +-
.../client/types/messages_and_media/audio.py | 2 +-
.../types/messages_and_media/contact.py | 2 +-
.../types/messages_and_media/document.py | 2 +-
.../client/types/messages_and_media/game.py | 2 +-
.../types/messages_and_media/location.py | 2 +-
.../types/messages_and_media/message.py | 2 +-
.../messages_and_media/message_entity.py | 2 +-
.../types/messages_and_media/messages.py | 2 +-
.../client/types/messages_and_media/photo.py | 2 +-
.../types/messages_and_media/photo_size.py | 2 +-
.../client/types/messages_and_media/poll.py | 2 +-
.../types/messages_and_media/poll_option.py | 2 +-
.../types/messages_and_media/sticker.py | 2 +-
.../messages_and_media/user_profile_photos.py | 2 +-
.../client/types/messages_and_media/venue.py | 2 +-
.../client/types/messages_and_media/video.py | 2 +-
.../types/messages_and_media/video_note.py | 2 +-
.../client/types/messages_and_media/voice.py | 2 +-
pyrogram/client/types/user_and_chats/chat.py | 2 +-
.../types/user_and_chats/chat_member.py | 2 +-
.../types/user_and_chats/chat_members.py | 2 +-
.../types/user_and_chats/chat_permissions.py | 2 +-
.../client/types/user_and_chats/chat_photo.py | 2 +-
.../types/user_and_chats/chat_preview.py | 2 +-
.../client/types/user_and_chats/dialog.py | 2 +-
.../client/types/user_and_chats/dialogs.py | 2 +-
pyrogram/client/types/user_and_chats/user.py | 2 +-
.../types/user_and_chats/user_status.py | 2 +-
129 files changed, 153 insertions(+), 141 deletions(-)
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 24373ce1db1..f65ef231d0f 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -261,7 +261,7 @@ def proxy(self, value):
self._proxy.update(value)
def start(self):
- """Use this method to start the Client.
+ """Start the Client.
Raises:
RPCError: In case of a Telegram RPC error.
@@ -354,7 +354,7 @@ def start(self):
return self
def stop(self):
- """Use this method to stop the Client.
+ """Stop the Client.
Raises:
ConnectionError: In case you try to stop an already stopped Client.
@@ -396,7 +396,7 @@ def stop(self):
return self
def restart(self):
- """Use this method to restart the Client.
+ """Restart the Client.
Raises:
ConnectionError: In case you try to restart a stopped Client.
@@ -405,7 +405,7 @@ def restart(self):
self.start()
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
- """Use this method to block the main script execution until a signal (e.g.: from CTRL+C) is received.
+ """Block the main script execution until a signal (e.g.: from CTRL+C) is received.
Once the signal is received, the client will automatically stop and the main script will continue its execution.
This is used after starting one or more clients and is useful for event-driven applications only, that are,
@@ -438,7 +438,7 @@ def signal_handler(*args):
self.stop()
def run(self):
- """Use this method to start the Client and automatically idle the main script.
+ """Start the Client and automatically idle the main script.
This is a convenience method that literally just calls :meth:`start` and :meth:`idle`. It makes running a client
less verbose, but is not suitable in case you want to run more than one client in a single main script,
@@ -451,7 +451,7 @@ def run(self):
self.idle()
def add_handler(self, handler: Handler, group: int = 0):
- """Use this method to register an update handler.
+ """Register an update handler.
You can register multiple handlers, but at most one handler within a group
will be used for a single update. To handle the same update more than once, register
@@ -475,7 +475,7 @@ def add_handler(self, handler: Handler, group: int = 0):
return handler, group
def remove_handler(self, handler: Handler, group: int = 0):
- """Use this method to remove a previously-registered update handler.
+ """Remove a previously-registered update handler.
Make sure to provide the right group that the handler was added in. You can use
the return value of the :meth:`add_handler` method, a tuple of (handler, group), and
@@ -494,7 +494,7 @@ def remove_handler(self, handler: Handler, group: int = 0):
self.dispatcher.remove_handler(handler, group)
def stop_transmission(self):
- """Use this method to stop downloading or uploading a file.
+ """Stop downloading or uploading a file.
Must be called inside a progress callback function.
"""
raise Client.StopTransmission
@@ -1036,7 +1036,7 @@ def updates_worker(self):
log.debug("{} stopped".format(name))
def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
- """Use this method to send raw Telegram queries.
+ """Send raw Telegram queries.
This method makes it possible to manually call every single Telegram API method in a low-level manner.
Available functions are listed in the :obj:`functions ` package and may accept compound
@@ -1341,7 +1341,7 @@ def get_initial_dialogs(self):
self.get_initial_dialogs_chunk()
def resolve_peer(self, peer_id: Union[int, str]):
- """Use this method to get the InputPeer of a known peer id.
+ """Get the InputPeer of a known peer id.
Useful whenever an InputPeer type is required.
.. note::
@@ -1423,7 +1423,7 @@ def save_file(
progress: callable = None,
progress_args: tuple = ()
):
- """Use this method to upload a file onto Telegram servers, without actually sending the message to anyone.
+ """Upload a file onto Telegram servers, without actually sending the message to anyone.
Useful whenever an InputFile type is required.
.. note::
diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py
index 5070bd5238d..2117ec6e219 100644
--- a/pyrogram/client/filters/filters.py
+++ b/pyrogram/client/filters/filters.py
@@ -23,7 +23,7 @@
def create(name: str, func: callable, **kwargs) -> type:
- """Use this method to create a Filter.
+ """Create a Filter.
Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py
index 12effe47de9..010c29eac5f 100644
--- a/pyrogram/client/methods/bots/answer_callback_query.py
+++ b/pyrogram/client/methods/bots/answer_callback_query.py
@@ -29,7 +29,7 @@ def answer_callback_query(
url: str = None,
cache_time: int = 0
):
- """Use this method to send answers to callback queries sent from inline keyboards.
+ """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.
Parameters:
diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py
index 7a1b14c8310..62344f20470 100644
--- a/pyrogram/client/methods/bots/answer_inline_query.py
+++ b/pyrogram/client/methods/bots/answer_inline_query.py
@@ -34,7 +34,7 @@ def answer_inline_query(
switch_pm_text: str = "",
switch_pm_parameter: str = ""
):
- """Use this method to send answers to an inline query.
+ """Send answers to an inline query.
No more than 50 results per query are allowed.
Parameters:
diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py
index 64901fea4e8..e1472b9e2f8 100644
--- a/pyrogram/client/methods/bots/get_game_high_scores.py
+++ b/pyrogram/client/methods/bots/get_game_high_scores.py
@@ -30,7 +30,7 @@ def get_game_high_scores(
chat_id: Union[int, str],
message_id: int = None
) -> "pyrogram.GameHighScores":
- """Use this method to get data for high score tables.
+ """Get data for high score tables.
Parameters:
user_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py
index 14628b6429f..307238e34b0 100644
--- a/pyrogram/client/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/client/methods/bots/get_inline_bot_results.py
@@ -32,7 +32,7 @@ def get_inline_bot_results(
latitude: float = None,
longitude: float = None
):
- """Use this method to get bot results via inline queries.
+ """Get bot results via inline queries.
You can then send a result using :obj:`send_inline_bot_result `
Parameters:
diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py
index 7e57b39b1d7..443cb825472 100644
--- a/pyrogram/client/methods/bots/request_callback_answer.py
+++ b/pyrogram/client/methods/bots/request_callback_answer.py
@@ -30,7 +30,7 @@ def request_callback_answer(
callback_data: bytes,
timeout: int = 10
):
- """Use this method to request a callback answer from bots.
+ """Request a callback answer from bots.
This is the equivalent of clicking an inline button containing callback data.
Parameters:
diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py
index 03593a5669b..c10d328a35b 100644
--- a/pyrogram/client/methods/bots/send_game.py
+++ b/pyrogram/client/methods/bots/send_game.py
@@ -37,7 +37,7 @@ def send_game(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send a game.
+ """Send a game.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py
index 49dc11ee0f6..031591db4dc 100644
--- a/pyrogram/client/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/client/methods/bots/send_inline_bot_result.py
@@ -32,7 +32,7 @@ def send_inline_bot_result(
reply_to_message_id: int = None,
hide_via: bool = None
):
- """Use this method to send an inline bot result.
+ """Send an inline bot result.
Bot results can be retrieved using :obj:`get_inline_bot_results `
Parameters:
diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py
index f5658542959..3b0e97e23d8 100644
--- a/pyrogram/client/methods/bots/set_game_score.py
+++ b/pyrogram/client/methods/bots/set_game_score.py
@@ -34,7 +34,7 @@ def set_game_score(
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.
+ """Set the score of the specified user in a game.
Parameters:
user_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py
index f45a7dc2efd..88d975065db 100644
--- a/pyrogram/client/methods/chats/delete_chat_photo.py
+++ b/pyrogram/client/methods/chats/delete_chat_photo.py
@@ -27,7 +27,7 @@ def delete_chat_photo(
self,
chat_id: Union[int, str]
) -> bool:
- """Use this method to delete a chat photo.
+ """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.
diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py
index e1ca27c232d..a223a0006da 100644
--- a/pyrogram/client/methods/chats/export_chat_invite_link.py
+++ b/pyrogram/client/methods/chats/export_chat_invite_link.py
@@ -27,7 +27,7 @@ def export_chat_invite_link(
self,
chat_id: Union[int, str]
) -> str:
- """Use this method to generate a new invite link for a chat; any previously generated link is revoked.
+ """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.
diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py
index bf4c4ceaab5..96bc9eaf156 100644
--- a/pyrogram/client/methods/chats/get_chat.py
+++ b/pyrogram/client/methods/chats/get_chat.py
@@ -28,7 +28,7 @@ def get_chat(
self,
chat_id: Union[int, str]
) -> "pyrogram.Chat":
- """Use this method to get up to date information about the chat.
+ """Get up to date information about the chat.
Information include current name of the user for one-on-one conversations, current username of a user, group or
channel, etc.
diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py
index b625e32f0c5..c77e46b656f 100644
--- a/pyrogram/client/methods/chats/get_chat_member.py
+++ b/pyrogram/client/methods/chats/get_chat_member.py
@@ -30,7 +30,7 @@ def get_chat_member(
chat_id: Union[int, str],
user_id: Union[int, str]
) -> "pyrogram.ChatMember":
- """Use this method to get information about one member of a chat.
+ """Get information about one member of a chat.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py
index 79001614bf0..10f76dcf375 100644
--- a/pyrogram/client/methods/chats/get_chat_members.py
+++ b/pyrogram/client/methods/chats/get_chat_members.py
@@ -46,7 +46,7 @@ def get_chat_members(
query: str = "",
filter: str = Filters.ALL
) -> "pyrogram.ChatMembers":
- """Use this method to get a chunk of the members list of a chat.
+ """Get a chunk of the members list of a chat.
You can get up to 200 chat members at once.
A chat can be either a basic group, a supergroup or a channel.
diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py
index d40585f53d4..c8bd6deb79e 100644
--- a/pyrogram/client/methods/chats/get_chat_members_count.py
+++ b/pyrogram/client/methods/chats/get_chat_members_count.py
@@ -27,7 +27,7 @@ def get_chat_members_count(
self,
chat_id: Union[int, str]
) -> int:
- """Use this method to get the number of members in a chat.
+ """Get the number of members in a chat.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/chats/get_chat_preview.py b/pyrogram/client/methods/chats/get_chat_preview.py
index fd00c3740d1..8551aaf434c 100644
--- a/pyrogram/client/methods/chats/get_chat_preview.py
+++ b/pyrogram/client/methods/chats/get_chat_preview.py
@@ -26,7 +26,7 @@ def get_chat_preview(
self,
invite_link: str
):
- """Use this method to get the preview of a chat using the invite link.
+ """Get the preview of a chat using the invite link.
This method only returns a chat preview, if you want to join a chat use :meth:`join_chat`
diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py
index 83732d0708b..605ee782b16 100644
--- a/pyrogram/client/methods/chats/get_dialogs.py
+++ b/pyrogram/client/methods/chats/get_dialogs.py
@@ -34,7 +34,7 @@ def get_dialogs(
limit: int = 100,
pinned_only: bool = False
) -> "pyrogram.Dialogs":
- """Use this method to get a chunk of the user's dialogs.
+ """Get a chunk of the user's dialogs.
You can get up to 100 dialogs at once.
For a more convenient way of getting a user's dialogs see :meth:`iter_dialogs`.
diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py
index eea327da29a..1a307433f98 100644
--- a/pyrogram/client/methods/chats/get_dialogs_count.py
+++ b/pyrogram/client/methods/chats/get_dialogs_count.py
@@ -22,7 +22,7 @@
class GetDialogsCount(BaseClient):
def get_dialogs_count(self, pinned_only: bool = False) -> int:
- """Use this method to get the total count of your dialogs.
+ """Get the total count of your dialogs.
pinned_only (``bool``, *optional*):
Pass True if you want to count only pinned dialogs.
diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py
index f735da482fe..330eed7b7f1 100644
--- a/pyrogram/client/methods/chats/iter_chat_members.py
+++ b/pyrogram/client/methods/chats/iter_chat_members.py
@@ -45,7 +45,7 @@ def iter_chat_members(
query: str = "",
filter: str = Filters.ALL
) -> Generator["pyrogram.ChatMember", None, None]:
- """Use this method to iterate through the members of a chat sequentially.
+ """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
from the hassle of setting up boilerplate code. It is useful for getting the whole members list of a chat with
diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py
index 6fc9f81c9fb..1209a5df367 100644
--- a/pyrogram/client/methods/chats/iter_dialogs.py
+++ b/pyrogram/client/methods/chats/iter_dialogs.py
@@ -28,7 +28,7 @@ def iter_dialogs(
offset_date: int = 0,
limit: int = 0
) -> Generator["pyrogram.Dialog", None, None]:
- """Use this method to iterate through a user's dialogs sequentially.
+ """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.
diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py
index 77b2ee50d10..ed6c69ceec9 100644
--- a/pyrogram/client/methods/chats/join_chat.py
+++ b/pyrogram/client/methods/chats/join_chat.py
@@ -26,7 +26,7 @@ def join_chat(
self,
chat_id: str
):
- """Use this method to join a group chat or channel.
+ """Join a group chat or channel.
Parameters:
chat_id (``str``):
diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py
index dbd095ad817..f50588290c0 100644
--- a/pyrogram/client/methods/chats/kick_chat_member.py
+++ b/pyrogram/client/methods/chats/kick_chat_member.py
@@ -30,7 +30,7 @@ def kick_chat_member(
user_id: Union[int, str],
until_date: int = 0
) -> Union["pyrogram.Message", bool]:
- """Use this method to kick a user from a group, a supergroup or a channel.
+ """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
have the appropriate admin rights.
diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py
index 57cc1090d36..3ed6f10fa32 100644
--- a/pyrogram/client/methods/chats/leave_chat.py
+++ b/pyrogram/client/methods/chats/leave_chat.py
@@ -28,7 +28,7 @@ def leave_chat(
chat_id: Union[int, str],
delete: bool = False
):
- """Use this method to leave a group chat or channel.
+ """Leave a group chat or channel.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py
index 2c485dabe39..efb41e674df 100644
--- a/pyrogram/client/methods/chats/pin_chat_message.py
+++ b/pyrogram/client/methods/chats/pin_chat_message.py
@@ -29,7 +29,7 @@ def pin_chat_message(
message_id: int,
disable_notification: bool = None
) -> bool:
- """Use this method to pin a message in a group, channel or your own chat.
+ """Pin a message in a group, channel or your own chat.
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.
diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py
index 0b93576abe2..700b3a68f9a 100644
--- a/pyrogram/client/methods/chats/promote_chat_member.py
+++ b/pyrogram/client/methods/chats/promote_chat_member.py
@@ -36,7 +36,7 @@ def promote_chat_member(
can_pin_messages: bool = False,
can_promote_members: bool = False
) -> bool:
- """Use this method to promote or demote a user in a supergroup or a channel.
+ """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.
diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/restrict_chat.py
index 9f6d291001f..8e63a9b2e31 100644
--- a/pyrogram/client/methods/chats/restrict_chat.py
+++ b/pyrogram/client/methods/chats/restrict_chat.py
@@ -36,7 +36,7 @@ def restrict_chat(
can_invite_users: bool = False,
can_pin_messages: bool = False
) -> Chat:
- """Use this method to restrict a chat.
+ """Restrict a chat.
Pass True for all boolean parameters to lift restrictions from a chat.
Parameters:
diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py
index 1f31d8eb614..96e07d183cf 100644
--- a/pyrogram/client/methods/chats/restrict_chat_member.py
+++ b/pyrogram/client/methods/chats/restrict_chat_member.py
@@ -38,7 +38,7 @@ def restrict_chat_member(
can_invite_users: bool = False,
can_pin_messages: bool = False
) -> Chat:
- """Use this method to restrict a user in a supergroup.
+ """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.
diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py
index 6cc3bbd35ce..68bf9fa24cf 100644
--- a/pyrogram/client/methods/chats/set_chat_description.py
+++ b/pyrogram/client/methods/chats/set_chat_description.py
@@ -28,7 +28,7 @@ def set_chat_description(
chat_id: Union[int, str],
description: str
) -> bool:
- """Use this method to change the description of a supergroup or a channel.
+ """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.
Parameters:
diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py
index 7248a2651d8..4a2f6cf11c6 100644
--- a/pyrogram/client/methods/chats/set_chat_photo.py
+++ b/pyrogram/client/methods/chats/set_chat_photo.py
@@ -31,7 +31,7 @@ def set_chat_photo(
chat_id: Union[int, str],
photo: str
) -> bool:
- """Use this method to set a new profile photo for the chat.
+ """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.
diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py
index a159d2cc17a..f70fa5dab05 100644
--- a/pyrogram/client/methods/chats/set_chat_title.py
+++ b/pyrogram/client/methods/chats/set_chat_title.py
@@ -28,7 +28,7 @@ def set_chat_title(
chat_id: Union[int, str],
title: str
) -> bool:
- """Use this method to change the title of a chat.
+ """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.
diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py
index 35ea0343ea3..7e4205c961b 100644
--- a/pyrogram/client/methods/chats/unban_chat_member.py
+++ b/pyrogram/client/methods/chats/unban_chat_member.py
@@ -28,7 +28,7 @@ def unban_chat_member(
chat_id: Union[int, str],
user_id: Union[int, str]
) -> bool:
- """Use this method to unban a previously kicked user in a supergroup or channel.
+ """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.
diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py
index 4e2531fd78a..2263931561a 100644
--- a/pyrogram/client/methods/chats/unpin_chat_message.py
+++ b/pyrogram/client/methods/chats/unpin_chat_message.py
@@ -27,7 +27,7 @@ def unpin_chat_message(
self,
chat_id: Union[int, str]
) -> bool:
- """Use this method to unpin a message in a group, channel or your own chat.
+ """Unpin a message in a group, channel or your own chat.
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.
diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py
index 2e8adb05620..de5015ea4f3 100644
--- a/pyrogram/client/methods/chats/update_chat_username.py
+++ b/pyrogram/client/methods/chats/update_chat_username.py
@@ -28,7 +28,7 @@ def update_chat_username(
chat_id: Union[int, str],
username: Union[str, None]
) -> bool:
- """Use this method to update a channel or a supergroup username.
+ """Update a channel or a supergroup username.
To update your own username (for users only, not bots) you can use :meth:`update_username`.
diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py
index aa8e1fd5d15..c7e647b04be 100644
--- a/pyrogram/client/methods/contacts/add_contacts.py
+++ b/pyrogram/client/methods/contacts/add_contacts.py
@@ -28,7 +28,7 @@ def add_contacts(
self,
contacts: List["pyrogram.InputPhoneContact"]
):
- """Use this method to add contacts to your Telegram address book.
+ """Add contacts to your Telegram address book.
Parameters:
contacts (List of :obj:`InputPhoneContact`):
diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py
index db5b9df1eee..7a5ecf5552b 100644
--- a/pyrogram/client/methods/contacts/delete_contacts.py
+++ b/pyrogram/client/methods/contacts/delete_contacts.py
@@ -28,7 +28,7 @@ def delete_contacts(
self,
ids: List[int]
):
- """Use this method to delete contacts from your Telegram address book.
+ """Delete contacts from your Telegram address book.
Parameters:
ids (List of ``int``):
diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py
index 7e607e776cb..1fa5b7389d6 100644
--- a/pyrogram/client/methods/contacts/get_contacts.py
+++ b/pyrogram/client/methods/contacts/get_contacts.py
@@ -30,7 +30,7 @@
class GetContacts(BaseClient):
def get_contacts(self) -> List["pyrogram.User"]:
- """Use this method to get contacts from your Telegram address book.
+ """Get contacts from your Telegram address book.
Returns:
List of :obj:`User`: On success, a list of users is returned.
diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py
index b9e6f6c4f48..01fb0789b6a 100644
--- a/pyrogram/client/methods/contacts/get_contacts_count.py
+++ b/pyrogram/client/methods/contacts/get_contacts_count.py
@@ -22,7 +22,7 @@
class GetContactsCount(BaseClient):
def get_contacts_count(self) -> int:
- """Use this method to get the total count of contacts from your Telegram address book.
+ """Get the total count of contacts from your Telegram address book.
Returns:
``int``: On success, an integer is returned.
diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py
index 067d2fae40d..3667c8ee0bc 100644
--- a/pyrogram/client/methods/messages/delete_messages.py
+++ b/pyrogram/client/methods/messages/delete_messages.py
@@ -29,7 +29,7 @@ def delete_messages(
message_ids: Iterable[int],
revoke: bool = True
) -> bool:
- """Use this method to delete messages, including service messages.
+ """Delete messages, including service messages.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py
index 04a5ec57ff7..5c4401739d7 100644
--- a/pyrogram/client/methods/messages/download_media.py
+++ b/pyrogram/client/methods/messages/download_media.py
@@ -32,7 +32,7 @@ def download_media(
progress: callable = None,
progress_args: tuple = ()
) -> Union[str, None]:
- """Use this method to download the media from a message.
+ """Download the media from a message.
Parameters:
message (:obj:`Message` | ``str``):
diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py
index fe19dcc9777..cfe5b23646c 100644
--- a/pyrogram/client/methods/messages/edit_message_caption.py
+++ b/pyrogram/client/methods/messages/edit_message_caption.py
@@ -32,7 +32,7 @@ def edit_message_caption(
parse_mode: str = "",
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
- """Use this method to edit captions of messages.
+ """Edit captions of messages.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py
index b03fb1e08f0..b15daa9b500 100644
--- a/pyrogram/client/methods/messages/edit_message_media.py
+++ b/pyrogram/client/methods/messages/edit_message_media.py
@@ -40,7 +40,7 @@ def edit_message_media(
media: InputMedia,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
- """Use this method to edit audio, document, photo, or video messages.
+ """Edit audio, document, photo, or video messages.
If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise,
message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded.
diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py
index 735614512de..8d2b82af93e 100644
--- a/pyrogram/client/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py
@@ -30,7 +30,7 @@ def edit_message_reply_markup(
message_id: int,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
- """Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots).
+ """Edit only the reply markup of messages sent by the bot or via the bot (for inline bots).
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py
index 30b58b68938..69283e8983e 100644
--- a/pyrogram/client/methods/messages/edit_message_text.py
+++ b/pyrogram/client/methods/messages/edit_message_text.py
@@ -33,7 +33,7 @@ def edit_message_text(
disable_web_page_preview: bool = None,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Message":
- """Use this method to edit text messages.
+ """Edit text messages.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py
index 08cc48ea4b6..a3e161fb3c2 100644
--- a/pyrogram/client/methods/messages/forward_messages.py
+++ b/pyrogram/client/methods/messages/forward_messages.py
@@ -33,7 +33,7 @@ def forward_messages(
as_copy: bool = False,
remove_caption: bool = False
) -> "pyrogram.Messages":
- """Use this method to forward messages of any kind.
+ """Forward messages of any kind.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py
index ffd8f9e0a6a..3933e7294f8 100644
--- a/pyrogram/client/methods/messages/get_history.py
+++ b/pyrogram/client/methods/messages/get_history.py
@@ -38,7 +38,7 @@ def get_history(
offset_date: int = 0,
reverse: bool = False
) -> "pyrogram.Messages":
- """Use this method to retrieve a chunk of the history of a chat.
+ """Retrieve a chunk of the history of a chat.
You can get up to 100 messages at once.
For a more convenient way of getting a chat history see :meth:`iter_history`.
diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py
index b4812d26c24..ced46799038 100644
--- a/pyrogram/client/methods/messages/get_history_count.py
+++ b/pyrogram/client/methods/messages/get_history_count.py
@@ -32,7 +32,7 @@ def get_history_count(
self,
chat_id: Union[int, str]
) -> int:
- """Use this method to get the total count of messages in a chat.
+ """Get the total count of messages in a chat.
.. note::
diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py
index 391c251c820..51f7135298d 100644
--- a/pyrogram/client/methods/messages/get_messages.py
+++ b/pyrogram/client/methods/messages/get_messages.py
@@ -36,7 +36,7 @@ def get_messages(
reply_to_message_ids: Union[int, Iterable[int]] = None,
replies: int = 1
) -> Union["pyrogram.Message", "pyrogram.Messages"]:
- """Use this method to get one or more messages that belong to a specific chat.
+ """Get one or more messages that belong to a specific chat.
You can retrieve up to 200 messages at once.
Parameters:
diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py
index b15463183a4..218dd7f5fa2 100644
--- a/pyrogram/client/methods/messages/iter_history.py
+++ b/pyrogram/client/methods/messages/iter_history.py
@@ -32,7 +32,7 @@ def iter_history(
offset_date: int = 0,
reverse: bool = False
) -> Generator["pyrogram.Message", None, None]:
- """Use this method to iterate through a chat history sequentially.
+ """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
the hassle of setting up boilerplate code. It is useful for getting the whole chat history with a single call.
diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py
index 929298df08b..b52181a6dfd 100644
--- a/pyrogram/client/methods/messages/retract_vote.py
+++ b/pyrogram/client/methods/messages/retract_vote.py
@@ -29,7 +29,7 @@ def retract_vote(
chat_id: Union[int, str],
message_id: int
) -> "pyrogram.Poll":
- """Use this method to retract your vote in a poll.
+ """Retract your vote in a poll.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py
index 4e21e56cfa8..461ef6fc96d 100644
--- a/pyrogram/client/methods/messages/send_animation.py
+++ b/pyrogram/client/methods/messages/send_animation.py
@@ -49,7 +49,7 @@ def send_animation(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound).
+ """Send animation files (animation or H.264/MPEG-4 AVC video without sound).
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py
index 71458c3316a..aaa5a529497 100644
--- a/pyrogram/client/methods/messages/send_audio.py
+++ b/pyrogram/client/methods/messages/send_audio.py
@@ -49,7 +49,7 @@ def send_audio(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send audio files.
+ """Send audio files.
For sending voice messages, use the :obj:`send_voice()` method instead.
diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py
index b0b3fc4ea94..9f66e5fd3ce 100644
--- a/pyrogram/client/methods/messages/send_cached_media.py
+++ b/pyrogram/client/methods/messages/send_cached_media.py
@@ -42,7 +42,7 @@ def send_cached_media(
"pyrogram.ForceReply"
] = None
) -> Union["pyrogram.Message", None]:
- """Use this method to send any media stored on the Telegram servers using a file_id.
+ """Send any media stored on the Telegram servers using a file_id.
This convenience method works with any valid file_id only.
It does the same as calling the relevant method for sending media using a file_id, thus saving you from the
diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py
index 1d8197474a2..ac55c63365c 100644
--- a/pyrogram/client/methods/messages/send_chat_action.py
+++ b/pyrogram/client/methods/messages/send_chat_action.py
@@ -44,7 +44,7 @@ class ChatAction:
class SendChatAction(BaseClient):
def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool:
- """Use this method when you need to tell the other party that something is happening on your side.
+ """Tell the other party that something is happening on your side.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py
index 27df6505f88..d0b6fb58aa0 100644
--- a/pyrogram/client/methods/messages/send_contact.py
+++ b/pyrogram/client/methods/messages/send_contact.py
@@ -40,7 +40,7 @@ def send_contact(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send phone contacts.
+ """Send phone contacts.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py
index e2ac0d23b06..e966a11ae8d 100644
--- a/pyrogram/client/methods/messages/send_document.py
+++ b/pyrogram/client/methods/messages/send_document.py
@@ -46,7 +46,7 @@ def send_document(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send general files.
+ """Send general files.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py
index dce6671b8e0..2e3681e6cf5 100644
--- a/pyrogram/client/methods/messages/send_location.py
+++ b/pyrogram/client/methods/messages/send_location.py
@@ -38,7 +38,7 @@ def send_location(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send points on the map.
+ """Send points on the map.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py
index fe6725f0450..0af94cf9afd 100644
--- a/pyrogram/client/methods/messages/send_media_group.py
+++ b/pyrogram/client/methods/messages/send_media_group.py
@@ -41,7 +41,7 @@ def send_media_group(
disable_notification: bool = None,
reply_to_message_id: int = None
):
- """Use this method to send a group of photos or videos as an album.
+ """Send a group of photos or videos as an album.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py
index 15c1487ab68..f8caa081c1f 100644
--- a/pyrogram/client/methods/messages/send_message.py
+++ b/pyrogram/client/methods/messages/send_message.py
@@ -39,7 +39,7 @@ def send_message(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send text messages.
+ """Send text messages.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py
index 5487761e9d3..7c4f688fbe9 100644
--- a/pyrogram/client/methods/messages/send_photo.py
+++ b/pyrogram/client/methods/messages/send_photo.py
@@ -46,7 +46,7 @@ def send_photo(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send photos.
+ """Send photos.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py
index ff5c8533bd7..4dae53b2626 100644
--- a/pyrogram/client/methods/messages/send_poll.py
+++ b/pyrogram/client/methods/messages/send_poll.py
@@ -38,7 +38,7 @@ def send_poll(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send a new poll.
+ """Send a new poll.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py
index 0e314641991..cabe3487cca 100644
--- a/pyrogram/client/methods/messages/send_sticker.py
+++ b/pyrogram/client/methods/messages/send_sticker.py
@@ -43,7 +43,7 @@ def send_sticker(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send .webp stickers.
+ """Send .webp stickers.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py
index 1045bdbd353..35545c9be0e 100644
--- a/pyrogram/client/methods/messages/send_venue.py
+++ b/pyrogram/client/methods/messages/send_venue.py
@@ -42,7 +42,7 @@ def send_venue(
"pyrogram.ForceReply"
] = None
) -> "pyrogram.Message":
- """Use this method to send information about a venue.
+ """Send information about a venue.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py
index baa91d29634..92066e19d1a 100644
--- a/pyrogram/client/methods/messages/send_video.py
+++ b/pyrogram/client/methods/messages/send_video.py
@@ -50,7 +50,7 @@ def send_video(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send video files.
+ """Send video files.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py
index 9f934efe2cc..8886c58817c 100644
--- a/pyrogram/client/methods/messages/send_video_note.py
+++ b/pyrogram/client/methods/messages/send_video_note.py
@@ -46,7 +46,7 @@ def send_video_note(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send video messages.
+ """Send video messages.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py
index e7ec2b11bb1..3631d828817 100644
--- a/pyrogram/client/methods/messages/send_voice.py
+++ b/pyrogram/client/methods/messages/send_voice.py
@@ -46,7 +46,7 @@ def send_voice(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Use this method to send audio files.
+ """Send audio files.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/client/methods/messages/stop_poll.py
index 4179cce48da..6abe6791a86 100644
--- a/pyrogram/client/methods/messages/stop_poll.py
+++ b/pyrogram/client/methods/messages/stop_poll.py
@@ -30,7 +30,7 @@ def stop_poll(
message_id: int,
reply_markup: "pyrogram.InlineKeyboardMarkup" = None
) -> "pyrogram.Poll":
- """Use this method to stop a poll which was sent by you.
+ """Stop a poll which was sent by you.
Stopped polls can't be reopened and nobody will be able to vote in it anymore.
diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py
index 5e7b8ce863a..a5d77d86383 100644
--- a/pyrogram/client/methods/messages/vote_poll.py
+++ b/pyrogram/client/methods/messages/vote_poll.py
@@ -30,7 +30,7 @@ def vote_poll(
message_id: id,
option: int
) -> "pyrogram.Poll":
- """Use this method to vote a poll.
+ """Vote a poll.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py
index b9ee37d5597..a33b83c726c 100644
--- a/pyrogram/client/methods/password/change_cloud_password.py
+++ b/pyrogram/client/methods/password/change_cloud_password.py
@@ -30,7 +30,7 @@ def change_cloud_password(
new_password: str,
new_hint: str = ""
) -> bool:
- """Use this method to change your Two-Step Verification password (Cloud Password) with a new one.
+ """Change your Two-Step Verification password (Cloud Password) with a new one.
Parameters:
current_password (``str``):
diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py
index e2e05633116..23ee1608a0c 100644
--- a/pyrogram/client/methods/password/enable_cloud_password.py
+++ b/pyrogram/client/methods/password/enable_cloud_password.py
@@ -30,7 +30,7 @@ def enable_cloud_password(
hint: str = "",
email: str = None
) -> bool:
- """Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account.
+ """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.
diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py
index 37160530fb4..9dcbb005fab 100644
--- a/pyrogram/client/methods/password/remove_cloud_password.py
+++ b/pyrogram/client/methods/password/remove_cloud_password.py
@@ -26,7 +26,7 @@ def remove_cloud_password(
self,
password: str
) -> bool:
- """Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account.
+ """Turn off the Two-Step Verification security feature (Cloud Password) on your account.
Parameters:
password (``str``):
diff --git a/pyrogram/client/methods/users/delete_user_profile_photos.py b/pyrogram/client/methods/users/delete_user_profile_photos.py
index 4e962245758..305fd554cf7 100644
--- a/pyrogram/client/methods/users/delete_user_profile_photos.py
+++ b/pyrogram/client/methods/users/delete_user_profile_photos.py
@@ -29,7 +29,7 @@ def delete_user_profile_photos(
self,
id: Union[str, List[str]]
) -> bool:
- """Use this method to delete your own profile photos.
+ """Delete your own profile photos.
Parameters:
id (``str`` | ``list``):
diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py
index 03d3cdf1b38..44f16af38cd 100644
--- a/pyrogram/client/methods/users/get_me.py
+++ b/pyrogram/client/methods/users/get_me.py
@@ -23,7 +23,7 @@
class GetMe(BaseClient):
def get_me(self) -> "pyrogram.User":
- """A simple method for testing your authorization. Requires no parameters.
+ """Get your own user identity.
Returns:
:obj:`User`: Basic information about the user or bot.
diff --git a/pyrogram/client/methods/users/get_user_profile_photos.py b/pyrogram/client/methods/users/get_user_profile_photos.py
index 41dd2593cbf..ab58dbeca56 100644
--- a/pyrogram/client/methods/users/get_user_profile_photos.py
+++ b/pyrogram/client/methods/users/get_user_profile_photos.py
@@ -30,7 +30,7 @@ def get_user_profile_photos(
offset: int = 0,
limit: int = 100
) -> "pyrogram.UserProfilePhotos":
- """Use this method to get a list of profile pictures for a user.
+ """Get a list of profile pictures for a user.
Parameters:
user_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/users/get_user_profile_photos_count.py b/pyrogram/client/methods/users/get_user_profile_photos_count.py
index 595352c3da3..f552658400b 100644
--- a/pyrogram/client/methods/users/get_user_profile_photos_count.py
+++ b/pyrogram/client/methods/users/get_user_profile_photos_count.py
@@ -24,7 +24,7 @@
class GetUserProfilePhotosCount(BaseClient):
def get_user_profile_photos_count(self, user_id: Union[int, str]) -> int:
- """Use this method to get the total count of profile pictures for a user.
+ """Get the total count of profile pictures for a user.
Parameters:
user_id (``int`` | ``str``):
diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py
index bbcdebae55e..e95fedc783a 100644
--- a/pyrogram/client/methods/users/get_users.py
+++ b/pyrogram/client/methods/users/get_users.py
@@ -28,7 +28,7 @@ def get_users(
self,
user_ids: Union[Iterable[Union[int, str]], int, str]
) -> Union["pyrogram.User", List["pyrogram.User"]]:
- """Use this method to get information about a user.
+ """Get information about a user.
You can retrieve up to 200 users at once.
Parameters:
diff --git a/pyrogram/client/methods/users/set_user_profile_photo.py b/pyrogram/client/methods/users/set_user_profile_photo.py
index e873cf40dc7..bc056466afe 100644
--- a/pyrogram/client/methods/users/set_user_profile_photo.py
+++ b/pyrogram/client/methods/users/set_user_profile_photo.py
@@ -25,7 +25,7 @@ def set_user_profile_photo(
self,
photo: str
) -> bool:
- """Use this method to set a new profile photo.
+ """Set a new profile photo.
This method only works for Users.
Bots profile photos must be set using BotFather.
diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py
index 8b5b37ae01c..65b8617457c 100644
--- a/pyrogram/client/methods/users/update_username.py
+++ b/pyrogram/client/methods/users/update_username.py
@@ -27,7 +27,7 @@ def update_username(
self,
username: Union[str, None]
) -> bool:
- """Use this method to update your own username.
+ """Update your own username.
This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating
them from scratch using BotFather. To update a channel or supergroup username you can use
diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py
index a5190452af3..ab546b5ead0 100644
--- a/pyrogram/client/types/inline_mode/inline_query.py
+++ b/pyrogram/client/types/inline_mode/inline_query.py
@@ -28,7 +28,8 @@
class InlineQuery(PyrogramType, Update):
- """This object represents an incoming inline query.
+ """An incoming inline query.
+
When the user sends an empty query, your bot could return some default or trending results.
Parameters:
diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py
index 3e7fcb02b25..c9a46ff27c7 100644
--- a/pyrogram/client/types/inline_mode/inline_query_result.py
+++ b/pyrogram/client/types/inline_mode/inline_query_result.py
@@ -40,7 +40,7 @@
class InlineQueryResult(PyrogramType):
- """This object represents one result of an inline query.
+ """One result of an inline query.
Pyrogram currently supports results of the following 20 types:
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 bc0b4e2004c..8543eb4cc31 100644
--- a/pyrogram/client/types/inline_mode/inline_query_result_article.py
+++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py
@@ -23,7 +23,7 @@
class InlineQueryResultArticle(InlineQueryResult):
- """Represents a link to an article or web page.
+ """Link to an article or web page.
TODO: Hide url?
diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py
index 1e0fbc01544..551ca639137 100644
--- a/pyrogram/client/types/input_media/input_media.py
+++ b/pyrogram/client/types/input_media/input_media.py
@@ -20,7 +20,9 @@
class InputMedia(PyrogramType):
- """This object represents the content of a media message to be sent. It should be one of:
+ """Content of a media message to be sent.
+
+ It should be one of:
- :obj:`InputMediaAnimation`
- :obj:`InputMediaDocument`
diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py
index 177bd9ad916..23fcb9679b0 100644
--- a/pyrogram/client/types/input_media/input_media_animation.py
+++ b/pyrogram/client/types/input_media/input_media_animation.py
@@ -20,7 +20,7 @@
class InputMediaAnimation(InputMedia):
- """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
+ """An animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent inside an album.
Parameters:
media (``str``):
diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py
index 152532fa6a6..3fb45d8f046 100644
--- a/pyrogram/client/types/input_media/input_media_audio.py
+++ b/pyrogram/client/types/input_media/input_media_audio.py
@@ -20,7 +20,8 @@
class InputMediaAudio(InputMedia):
- """This object represents an audio to be sent inside an album.
+ """An audio to be sent inside an album.
+
It is intended to be used with :obj:`send_media_group() `.
Parameters:
diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py
index 046b731cd83..0de8dedf021 100644
--- a/pyrogram/client/types/input_media/input_media_document.py
+++ b/pyrogram/client/types/input_media/input_media_document.py
@@ -20,7 +20,7 @@
class InputMediaDocument(InputMedia):
- """This object represents a general file to be sent.
+ """A generic file to be sent inside an album.
Parameters:
media (``str``):
diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py
index 2797bd5f29b..ce134af2da1 100644
--- a/pyrogram/client/types/input_media/input_media_photo.py
+++ b/pyrogram/client/types/input_media/input_media_photo.py
@@ -20,7 +20,7 @@
class InputMediaPhoto(InputMedia):
- """This object represents a photo to be sent inside an album.
+ """A photo to be sent inside an album.
It is intended to be used with :obj:`send_media_group() `.
Parameters:
diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py
index 319973de703..9764dd1a88d 100644
--- a/pyrogram/client/types/input_media/input_media_video.py
+++ b/pyrogram/client/types/input_media/input_media_video.py
@@ -20,7 +20,7 @@
class InputMediaVideo(InputMedia):
- """This object represents a video to be sent inside an album.
+ """A video to be sent inside an album.
It is intended to be used with :obj:`send_media_group() `.
Parameters:
diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py
index 02189011c49..0b6353b7a71 100644
--- a/pyrogram/client/types/input_media/input_phone_contact.py
+++ b/pyrogram/client/types/input_media/input_phone_contact.py
@@ -22,7 +22,7 @@
class InputPhoneContact(PyrogramType):
- """This object represents a Phone Contact to be added in your Telegram address book.
+ """A Phone Contact to be added in your Telegram address book.
It is intended to be used with :meth:`add_contacts() `
Parameters:
diff --git a/pyrogram/client/types/input_message_content/input_message_content.py b/pyrogram/client/types/input_message_content/input_message_content.py
index f3e238b85b1..0cd264b7ede 100644
--- a/pyrogram/client/types/input_message_content/input_message_content.py
+++ b/pyrogram/client/types/input_message_content/input_message_content.py
@@ -24,7 +24,7 @@
class InputMessageContent(PyrogramType):
- """This object represents the content of a message to be sent as a result of an inline query.
+ """Content of a message to be sent as a result of an inline query.
Pyrogram currently supports the following 4 types:
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 feedd298e1d..4b294aab4d0 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
@@ -22,7 +22,7 @@
class InputTextMessageContent(InputMessageContent):
- """This object represents the content of a text message to be sent as the result of an inline query.
+ """Content of a text message to be sent as the result of an inline query.
Parameters:
message_text (``str``):
diff --git a/pyrogram/client/types/keyboards/callback_game.py b/pyrogram/client/types/keyboards/callback_game.py
index fc2d9884a97..b7397075ae9 100644
--- a/pyrogram/client/types/keyboards/callback_game.py
+++ b/pyrogram/client/types/keyboards/callback_game.py
@@ -20,7 +20,7 @@
class CallbackGame(PyrogramType):
- """A placeholder, currently holds no information.
+ """Placeholder, currently holds no information.
Use BotFather to set up your game.
"""
diff --git a/pyrogram/client/types/keyboards/callback_query.py b/pyrogram/client/types/keyboards/callback_query.py
index 61bbfe405f8..e58f77c2e58 100644
--- a/pyrogram/client/types/keyboards/callback_query.py
+++ b/pyrogram/client/types/keyboards/callback_query.py
@@ -27,7 +27,8 @@
class CallbackQuery(PyrogramType, Update):
- """This object represents an incoming callback query from a callback button in an inline keyboard.
+ """An incoming callback query from a callback button in an inline keyboard.
+
If the button that originated the query was attached to a message sent by the bot, the field message
will be present. If the button was attached to a message sent via the bot (in inline mode),
the field inline_message_id will be present. Exactly one of the fields data or game_short_name will be present.
diff --git a/pyrogram/client/types/keyboards/force_reply.py b/pyrogram/client/types/keyboards/force_reply.py
index f48dab141dc..f2d337b6758 100644
--- a/pyrogram/client/types/keyboards/force_reply.py
+++ b/pyrogram/client/types/keyboards/force_reply.py
@@ -21,7 +21,9 @@
class ForceReply(PyrogramType):
- """Upon receiving a message with this object, Telegram clients will display a reply interface to the user.
+ """Object used to force clients to show a reply interface.
+
+ Upon receiving a message with this object, Telegram clients will display a reply interface to the user.
This acts as if the user has selected the bot's message and tapped "Reply".
This can be extremely useful if you want to create user-friendly step-by-step interfaces without having to
diff --git a/pyrogram/client/types/keyboards/game_high_score.py b/pyrogram/client/types/keyboards/game_high_score.py
index 07db8e738e4..302dececdf8 100644
--- a/pyrogram/client/types/keyboards/game_high_score.py
+++ b/pyrogram/client/types/keyboards/game_high_score.py
@@ -24,7 +24,7 @@
class GameHighScore(PyrogramType):
- """This object represents one row of the high scores table for a game.
+ """One row of the high scores table for a game.
Parameters:
user (:obj:`User`):
diff --git a/pyrogram/client/types/keyboards/game_high_scores.py b/pyrogram/client/types/keyboards/game_high_scores.py
index 36b14c0f131..1c2cf105ba7 100644
--- a/pyrogram/client/types/keyboards/game_high_scores.py
+++ b/pyrogram/client/types/keyboards/game_high_scores.py
@@ -25,7 +25,7 @@
class GameHighScores(PyrogramType):
- """This object represents the high scores table for a game.
+ """The high scores table for a game.
Parameters:
total_count (``int``):
diff --git a/pyrogram/client/types/keyboards/inline_keyboard_button.py b/pyrogram/client/types/keyboards/inline_keyboard_button.py
index ea110f7f094..358eae21b03 100644
--- a/pyrogram/client/types/keyboards/inline_keyboard_button.py
+++ b/pyrogram/client/types/keyboards/inline_keyboard_button.py
@@ -27,7 +27,9 @@
class InlineKeyboardButton(PyrogramType):
- """This object represents one button of an inline keyboard. You must use exactly one of the optional fields.
+ """One button of an inline keyboard.
+
+ You must use exactly one of the optional fields.
Parameters:
text (``str``):
diff --git a/pyrogram/client/types/keyboards/inline_keyboard_markup.py b/pyrogram/client/types/keyboards/inline_keyboard_markup.py
index f48f4b8c4cc..c940fa1a21c 100644
--- a/pyrogram/client/types/keyboards/inline_keyboard_markup.py
+++ b/pyrogram/client/types/keyboards/inline_keyboard_markup.py
@@ -24,7 +24,7 @@
class InlineKeyboardMarkup(PyrogramType):
- """This object represents an inline keyboard that appears right next to the message it belongs to.
+ """An inline keyboard that appears right next to the message it belongs to.
Parameters:
inline_keyboard (List of List of :obj:`InlineKeyboardButton`):
diff --git a/pyrogram/client/types/keyboards/keyboard_button.py b/pyrogram/client/types/keyboards/keyboard_button.py
index e0393e3de92..405e37b52e1 100644
--- a/pyrogram/client/types/keyboards/keyboard_button.py
+++ b/pyrogram/client/types/keyboards/keyboard_button.py
@@ -22,7 +22,7 @@
class KeyboardButton(PyrogramType):
- """This object represents one button of the reply keyboard.
+ """One button of the reply keyboard.
For simple text buttons String can be used instead of this object to specify text of the button.
Optional fields are mutually exclusive.
diff --git a/pyrogram/client/types/keyboards/reply_keyboard_markup.py b/pyrogram/client/types/keyboards/reply_keyboard_markup.py
index 32c73c6b1de..85ab16f46b7 100644
--- a/pyrogram/client/types/keyboards/reply_keyboard_markup.py
+++ b/pyrogram/client/types/keyboards/reply_keyboard_markup.py
@@ -25,7 +25,7 @@
class ReplyKeyboardMarkup(PyrogramType):
- """This object represents a custom keyboard with reply options.
+ """A custom keyboard with reply options.
Parameters:
keyboard (List of List of :obj:`KeyboardButton`):
diff --git a/pyrogram/client/types/keyboards/reply_keyboard_remove.py b/pyrogram/client/types/keyboards/reply_keyboard_remove.py
index 560c89ee113..bb448447a0e 100644
--- a/pyrogram/client/types/keyboards/reply_keyboard_remove.py
+++ b/pyrogram/client/types/keyboards/reply_keyboard_remove.py
@@ -21,9 +21,12 @@
class ReplyKeyboardRemove(PyrogramType):
- """Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display the default letter-keyboard.
- By default, custom keyboards are displayed until a new keyboard is sent by a bot. An exception is made for one-time
- keyboards that are hidden immediately after the user presses a button (see ReplyKeyboardMarkup).
+ """Object used to tell clients to remove a bot keyboard.
+
+ Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display
+ the default letter-keyboard. By default, custom keyboards are displayed until a new keyboard is sent by a bot.
+ An exception is made for one-time keyboards that are hidden immediately after the user presses a button
+ (see ReplyKeyboardMarkup).
Parameters:
selective (``bool``, *optional*):
diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py
index 25dda78e49c..8d889cc2d37 100644
--- a/pyrogram/client/types/messages_and_media/animation.py
+++ b/pyrogram/client/types/messages_and_media/animation.py
@@ -26,7 +26,7 @@
class Animation(PyrogramType):
- """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound).
+ """An animation file (GIF or H.264/MPEG-4 AVC video without sound).
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py
index 9f024ee1fde..704f4f75646 100644
--- a/pyrogram/client/types/messages_and_media/audio.py
+++ b/pyrogram/client/types/messages_and_media/audio.py
@@ -26,7 +26,7 @@
class Audio(PyrogramType):
- """This object represents an audio file to be treated as music by the Telegram clients.
+ """An audio file to be treated as music by the Telegram clients.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py
index e2fba707652..fb4eb3a6eac 100644
--- a/pyrogram/client/types/messages_and_media/contact.py
+++ b/pyrogram/client/types/messages_and_media/contact.py
@@ -23,7 +23,7 @@
class Contact(PyrogramType):
- """This object represents a phone contact.
+ """A phone contact.
Parameters:
phone_number (``str``):
diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py
index c4c8ab16ca0..754fc5af4b1 100644
--- a/pyrogram/client/types/messages_and_media/document.py
+++ b/pyrogram/client/types/messages_and_media/document.py
@@ -26,7 +26,7 @@
class Document(PyrogramType):
- """This object represents a general file (as opposed to photos, voice messages, audio files, ...).
+ """A generic file (as opposed to photos, voice messages, audio files, ...).
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py
index 50268153fd3..b4c96a11a57 100644
--- a/pyrogram/client/types/messages_and_media/game.py
+++ b/pyrogram/client/types/messages_and_media/game.py
@@ -24,7 +24,7 @@
class Game(PyrogramType):
- """This object represents a game.
+ """A game.
Use BotFather to create and edit games, their short names will act as unique identifiers.
Parameters:
diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py
index 6af918f71fb..c3d8f974a93 100644
--- a/pyrogram/client/types/messages_and_media/location.py
+++ b/pyrogram/client/types/messages_and_media/location.py
@@ -23,7 +23,7 @@
class Location(PyrogramType):
- """This object represents a point on the map.
+ """A point on the map.
Parameters:
longitude (``float``):
diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py
index a4d759c8ee2..28a5d0ea277 100644
--- a/pyrogram/client/types/messages_and_media/message.py
+++ b/pyrogram/client/types/messages_and_media/message.py
@@ -60,7 +60,7 @@ def html(self):
class Message(PyrogramType, Update):
- """This object represents a message.
+ """A message.
Parameters:
message_id (``int``):
diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py
index 6fe4ef9189e..768dee1eecb 100644
--- a/pyrogram/client/types/messages_and_media/message_entity.py
+++ b/pyrogram/client/types/messages_and_media/message_entity.py
@@ -24,7 +24,7 @@
class MessageEntity(PyrogramType):
- """This object represents one special entity in a text message.
+ """One special entity in a text message.
For example, hashtags, usernames, URLs, etc.
Parameters:
diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py
index 4ce41aecf61..4f930a22630 100644
--- a/pyrogram/client/types/messages_and_media/messages.py
+++ b/pyrogram/client/types/messages_and_media/messages.py
@@ -27,7 +27,7 @@
class Messages(PyrogramType, Update):
- """This object represents a chat's messages.
+ """Contains a chat's messages.
Parameters:
total_count (``int``):
diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py
index f7f9f4ec0b3..c2d0eb1ff8e 100644
--- a/pyrogram/client/types/messages_and_media/photo.py
+++ b/pyrogram/client/types/messages_and_media/photo.py
@@ -28,7 +28,7 @@
class Photo(PyrogramType):
- """This object represents a Photo.
+ """A Photo.
Parameters:
id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py
index a154c077c97..c76ef914ec4 100644
--- a/pyrogram/client/types/messages_and_media/photo_size.py
+++ b/pyrogram/client/types/messages_and_media/photo_size.py
@@ -26,7 +26,7 @@
class PhotoSize(PyrogramType):
- """This object represents one size of a photo or a file/sticker thumbnail.
+ """One size of a photo or a file/sticker thumbnail.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py
index 602da364c32..8aa32137148 100644
--- a/pyrogram/client/types/messages_and_media/poll.py
+++ b/pyrogram/client/types/messages_and_media/poll.py
@@ -26,7 +26,7 @@
class Poll(PyrogramType, Update):
- """This object represents a Poll.
+ """A Poll.
Parameters:
id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py
index 31eed702f85..e594e3ca5d8 100644
--- a/pyrogram/client/types/messages_and_media/poll_option.py
+++ b/pyrogram/client/types/messages_and_media/poll_option.py
@@ -21,7 +21,7 @@
class PollOption(PyrogramType):
- """This object contains information about one answer option in a poll.
+ """Contains information about one answer option in a poll.
Parameters:
text (``str``):
diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py
index e6b8de35ec4..0a3a42d1999 100644
--- a/pyrogram/client/types/messages_and_media/sticker.py
+++ b/pyrogram/client/types/messages_and_media/sticker.py
@@ -28,7 +28,7 @@
class Sticker(PyrogramType):
- """This object represents a sticker.
+ """A sticker.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py
index 66bcb51a27c..c74a371b2d1 100644
--- a/pyrogram/client/types/messages_and_media/user_profile_photos.py
+++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py
@@ -24,7 +24,7 @@
class UserProfilePhotos(PyrogramType):
- """This object represents a user's profile pictures.
+ """Contains a user's profile pictures.
Parameters:
total_count (``int``):
diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py
index 61e3d91235b..cac84e57807 100644
--- a/pyrogram/client/types/messages_and_media/venue.py
+++ b/pyrogram/client/types/messages_and_media/venue.py
@@ -23,7 +23,7 @@
class Venue(PyrogramType):
- """This object represents a venue.
+ """A venue.
Parameters:
location (:obj:`Location`):
diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py
index 8c3e9c00198..13980270e58 100644
--- a/pyrogram/client/types/messages_and_media/video.py
+++ b/pyrogram/client/types/messages_and_media/video.py
@@ -26,7 +26,7 @@
class Video(PyrogramType):
- """This object represents a video file.
+ """A video file.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py
index 59c60c6ed32..5ebc8774cec 100644
--- a/pyrogram/client/types/messages_and_media/video_note.py
+++ b/pyrogram/client/types/messages_and_media/video_note.py
@@ -26,7 +26,7 @@
class VideoNote(PyrogramType):
- """This object represents a video note.
+ """A video note.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py
index 8780482687f..88154e2f5a1 100644
--- a/pyrogram/client/types/messages_and_media/voice.py
+++ b/pyrogram/client/types/messages_and_media/voice.py
@@ -25,7 +25,7 @@
class Voice(PyrogramType):
- """This object represents a voice note.
+ """A voice note.
Parameters:
file_id (``str``):
diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py
index 0c6210eb352..3b5f242b08d 100644
--- a/pyrogram/client/types/user_and_chats/chat.py
+++ b/pyrogram/client/types/user_and_chats/chat.py
@@ -26,7 +26,7 @@
class Chat(PyrogramType):
- """This object represents a chat.
+ """A chat.
Parameters:
id (``int``):
diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py
index 9841af83464..9de9986a28f 100644
--- a/pyrogram/client/types/user_and_chats/chat_member.py
+++ b/pyrogram/client/types/user_and_chats/chat_member.py
@@ -23,7 +23,7 @@
class ChatMember(PyrogramType):
- """This object contains information about one member of a chat.
+ """Contains information about one member of a chat.
Parameters:
user (:obj:`User`):
diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py
index d7cf3b9a289..8f98277ccec 100644
--- a/pyrogram/client/types/user_and_chats/chat_members.py
+++ b/pyrogram/client/types/user_and_chats/chat_members.py
@@ -25,7 +25,7 @@
class ChatMembers(PyrogramType):
- """This object contains information about the members list of a chat.
+ """Contains information about the members list of a chat.
Parameters:
total_count (``int``):
diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py
index ec00272eedd..6fa1a2a8f5e 100644
--- a/pyrogram/client/types/user_and_chats/chat_permissions.py
+++ b/pyrogram/client/types/user_and_chats/chat_permissions.py
@@ -23,7 +23,7 @@
class ChatPermissions(PyrogramType):
- """This object represents both a chat default permissions and a single member permissions within a chat.
+ """A chat default permissions and a single member permissions within a chat.
Some permissions make sense depending on the context: default chat permissions, restricted/kicked member or
administrators in groups or channels.
diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py
index af4f2df4714..3f1f7cc6543 100644
--- a/pyrogram/client/types/user_and_chats/chat_photo.py
+++ b/pyrogram/client/types/user_and_chats/chat_photo.py
@@ -25,7 +25,7 @@
class ChatPhoto(PyrogramType):
- """This object represents a chat photo.
+ """a chat photo.
Parameters:
small_file_id (``str``):
diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py
index b6b136a1238..880dcc8e363 100644
--- a/pyrogram/client/types/user_and_chats/chat_preview.py
+++ b/pyrogram/client/types/user_and_chats/chat_preview.py
@@ -26,7 +26,7 @@
class ChatPreview(PyrogramType):
- """This object represents a chat preview.
+ """A chat preview.
Parameters:
title (``str``):
diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py
index ec011d813a7..3dc09dbc7b6 100644
--- a/pyrogram/client/types/user_and_chats/dialog.py
+++ b/pyrogram/client/types/user_and_chats/dialog.py
@@ -24,7 +24,7 @@
class Dialog(PyrogramType):
- """This object represents a dialog.
+ """A user's dialog.
Parameters:
chat (:obj:`Chat `):
diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py
index 97ca462bba1..43b9f66778e 100644
--- a/pyrogram/client/types/user_and_chats/dialogs.py
+++ b/pyrogram/client/types/user_and_chats/dialogs.py
@@ -26,7 +26,7 @@
class Dialogs(PyrogramType):
- """This object represents a user's dialogs chunk.
+ """Contains a user's dialogs chunk.
Parameters:
total_count (``int``):
diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py
index 976ce5a0a14..c5a0976c094 100644
--- a/pyrogram/client/types/user_and_chats/user.py
+++ b/pyrogram/client/types/user_and_chats/user.py
@@ -24,7 +24,7 @@
class User(PyrogramType):
- """This object represents a Telegram user or bot.
+ """A Telegram user or bot.
Parameters:
id (``int``):
diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py
index 0f9c003ee58..f91c2924586 100644
--- a/pyrogram/client/types/user_and_chats/user_status.py
+++ b/pyrogram/client/types/user_and_chats/user_status.py
@@ -24,7 +24,7 @@
class UserStatus(PyrogramType, Update):
- """This object represents a User status (Last Seen privacy).
+ """A User status (Last Seen privacy).
.. note::
From 0e80b39c2c373de676cc00704da9d0486d64c401 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 15:57:49 +0200
Subject: [PATCH 0133/1652] Build a much cleaner errors reference page
---
docs/source/api/errors.rst | 77 ++++++++++++++++----
docs/source/errors/bad-request.rst | 7 --
docs/source/errors/flood.rst | 7 --
docs/source/errors/forbidden.rst | 7 --
docs/source/errors/internal-server-error.rst | 7 --
docs/source/errors/not-acceptable.rst | 7 --
docs/source/errors/see-other.rst | 7 --
docs/source/errors/unauthorized.rst | 7 --
docs/source/errors/unknown-error.rst | 7 --
docs/source/index.rst | 2 +-
docs/source/topics/faq.rst | 2 +-
11 files changed, 63 insertions(+), 74 deletions(-)
delete mode 100644 docs/source/errors/bad-request.rst
delete mode 100644 docs/source/errors/flood.rst
delete mode 100644 docs/source/errors/forbidden.rst
delete mode 100644 docs/source/errors/internal-server-error.rst
delete mode 100644 docs/source/errors/not-acceptable.rst
delete mode 100644 docs/source/errors/see-other.rst
delete mode 100644 docs/source/errors/unauthorized.rst
delete mode 100644 docs/source/errors/unknown-error.rst
diff --git a/docs/source/api/errors.rst b/docs/source/api/errors.rst
index 51d1fcd3d00..29dcaface35 100644
--- a/docs/source/api/errors.rst
+++ b/docs/source/api/errors.rst
@@ -1,28 +1,73 @@
RPC Errors
==========
-All the Pyrogram errors listed here live inside the ``errors`` sub-package.
+All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.errors``.
+The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram
+follow the usual *PascalCase* convention.
-**Example:**
+**Example**:
.. code-block:: python
- from pyrogram.errors import RPCError
+ from pyrogram.errors import InternalServerError
try:
...
- except RPCError:
+ except FloodWait:
...
-.. autoexception:: pyrogram.RPCError()
- :members:
-
-.. toctree::
- ../errors/see-other
- ../errors/bad-request
- ../errors/unauthorized
- ../errors/forbidden
- ../errors/not-acceptable
- ../errors/flood
- ../errors/internal-server-error
- ../errors/unknown-error
+303 - See Other
+---------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/303_SEE_OTHER.tsv
+ :delim: tab
+ :header-rows: 1
+
+400 - Bad Request
+-----------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/400_BAD_REQUEST.tsv
+ :delim: tab
+ :header-rows: 1
+
+401 - Unauthorized
+------------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/401_UNAUTHORIZED.tsv
+ :delim: tab
+ :header-rows: 1
+
+403 - Forbidden
+---------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/403_FORBIDDEN.tsv
+ :delim: tab
+ :header-rows: 1
+
+406 - Not Acceptable
+--------------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv
+ :delim: tab
+ :header-rows: 1
+
+420 - Flood
+-----------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/420_FLOOD.tsv
+ :delim: tab
+ :header-rows: 1
+
+500 - Internal Server Error
+---------------------------
+
+.. csv-table::
+ :file: ../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
+ :delim: tab
+ :header-rows: 1
diff --git a/docs/source/errors/bad-request.rst b/docs/source/errors/bad-request.rst
deleted file mode 100644
index 2d56434c385..00000000000
--- a/docs/source/errors/bad-request.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-400 - Bad Request
-=================
-
-.. module:: pyrogram.errors.BadRequest
-
-.. automodule:: pyrogram.errors.exceptions.bad_request_400
- :members:
diff --git a/docs/source/errors/flood.rst b/docs/source/errors/flood.rst
deleted file mode 100644
index 55098cbb809..00000000000
--- a/docs/source/errors/flood.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-420 - Flood
-===========
-
-.. module:: pyrogram.errors.Flood
-
-.. automodule:: pyrogram.errors.exceptions.flood_420
- :members:
diff --git a/docs/source/errors/forbidden.rst b/docs/source/errors/forbidden.rst
deleted file mode 100644
index cd794979297..00000000000
--- a/docs/source/errors/forbidden.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-403 - Forbidden
-===============
-
-.. module:: pyrogram.errors.Forbidden
-
-.. automodule:: pyrogram.errors.exceptions.forbidden_403
- :members:
diff --git a/docs/source/errors/internal-server-error.rst b/docs/source/errors/internal-server-error.rst
deleted file mode 100644
index 7f78d51945e..00000000000
--- a/docs/source/errors/internal-server-error.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-500 - Internal Server Error
-===========================
-
-.. module:: pyrogram.errors.InternalServerError
-
-.. automodule:: pyrogram.errors.exceptions.internal_server_error_500
- :members:
diff --git a/docs/source/errors/not-acceptable.rst b/docs/source/errors/not-acceptable.rst
deleted file mode 100644
index 5a8365fc6fa..00000000000
--- a/docs/source/errors/not-acceptable.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-406 - Not Acceptable
-====================
-
-.. module:: pyrogram.errors.NotAcceptable
-
-.. automodule:: pyrogram.errors.exceptions.not_acceptable_406
- :members:
diff --git a/docs/source/errors/see-other.rst b/docs/source/errors/see-other.rst
deleted file mode 100644
index f90902d0361..00000000000
--- a/docs/source/errors/see-other.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-303 - See Other
-===============
-
-.. module:: pyrogram.errors.SeeOther
-
-.. automodule:: pyrogram.errors.exceptions.see_other_303
- :members:
diff --git a/docs/source/errors/unauthorized.rst b/docs/source/errors/unauthorized.rst
deleted file mode 100644
index d47ed3fb1f9..00000000000
--- a/docs/source/errors/unauthorized.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-401 - Unauthorized
-==================
-
-.. module:: pyrogram.errors.Unauthorized
-
-.. automodule:: pyrogram.errors.exceptions.unauthorized_401
- :members:
diff --git a/docs/source/errors/unknown-error.rst b/docs/source/errors/unknown-error.rst
deleted file mode 100644
index 21495957d6d..00000000000
--- a/docs/source/errors/unknown-error.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-520 - Unknown Error
-===================
-
-.. module:: pyrogram.errors.UnknownError
-
-.. autoexception:: pyrogram.errors.rpc_error.UnknownError
- :members:
diff --git a/docs/source/index.rst b/docs/source/index.rst
index ae6a2fdab6b..81b4213ce18 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -157,4 +157,4 @@ Topics
:caption: Telegram API
telegram/functions/index
- telegram/types/index
+ telegram/types/index
\ No newline at end of file
diff --git a/docs/source/topics/faq.rst b/docs/source/topics/faq.rst
index f647e2612b7..5a8a39293f5 100644
--- a/docs/source/topics/faq.rst
+++ b/docs/source/topics/faq.rst
@@ -68,7 +68,7 @@ in a bunch of seconds:
I keep getting [400 PEER_ID_INVALID] error!
-------------------------------------------
-The error in question is ``[400 PEER_ID_INVALID]: The id/access_hash combination is invalid``, and could mean several
+The error in question is **[400 PEER_ID_INVALID]: The id/access_hash combination is invalid**, and could mean several
things:
- The chat id you tried to use is simply wrong, double check it.
From 65c07b7d3417189ab750842b6138357772fdd870 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 16:06:34 +0200
Subject: [PATCH 0134/1652] Use a better repr for all types now eval(repr(obj)
== obj
---
pyrogram/api/core/object.py | 62 +++++++++++--------
.../client/types/inline_mode/inline_query.py | 3 +-
.../types/inline_mode/inline_query_result.py | 2 +-
.../client/types/input_media/input_media.py | 2 +-
.../input_message_content.py | 2 +-
.../client/types/keyboards/callback_game.py | 2 +-
.../client/types/keyboards/callback_query.py | 2 +-
.../client/types/keyboards/force_reply.py | 2 +-
.../client/types/keyboards/game_high_score.py | 2 +-
.../types/keyboards/game_high_scores.py | 2 +-
.../types/keyboards/inline_keyboard_button.py | 2 +-
.../types/keyboards/inline_keyboard_markup.py | 2 +-
.../client/types/keyboards/keyboard_button.py | 2 +-
.../types/keyboards/reply_keyboard_markup.py | 2 +-
.../types/keyboards/reply_keyboard_remove.py | 2 +-
.../types/messages_and_media/animation.py | 2 +-
.../client/types/messages_and_media/audio.py | 2 +-
.../types/messages_and_media/contact.py | 2 +-
.../types/messages_and_media/document.py | 2 +-
.../client/types/messages_and_media/game.py | 2 +-
.../types/messages_and_media/location.py | 2 +-
.../types/messages_and_media/message.py | 4 +-
.../messages_and_media/message_entity.py | 2 +-
.../types/messages_and_media/messages.py | 2 +-
.../client/types/messages_and_media/photo.py | 2 +-
.../types/messages_and_media/photo_size.py | 2 +-
.../client/types/messages_and_media/poll.py | 2 +-
.../types/messages_and_media/poll_option.py | 9 ++-
.../types/messages_and_media/sticker.py | 2 +-
.../messages_and_media/user_profile_photos.py | 2 +-
.../client/types/messages_and_media/venue.py | 2 +-
.../client/types/messages_and_media/video.py | 2 +-
.../types/messages_and_media/video_note.py | 2 +-
.../client/types/messages_and_media/voice.py | 2 +-
pyrogram/client/types/pyrogram_type.py | 55 +++++++++-------
pyrogram/client/types/user_and_chats/chat.py | 2 +-
.../types/user_and_chats/chat_member.py | 2 +-
.../types/user_and_chats/chat_members.py | 2 +-
.../client/types/user_and_chats/chat_photo.py | 2 +-
.../types/user_and_chats/chat_preview.py | 2 +-
.../client/types/user_and_chats/dialog.py | 2 +-
.../client/types/user_and_chats/dialogs.py | 2 +-
pyrogram/client/types/user_and_chats/user.py | 2 +-
.../types/user_and_chats/user_status.py | 2 +-
44 files changed, 116 insertions(+), 95 deletions(-)
diff --git a/pyrogram/api/core/object.py b/pyrogram/api/core/object.py
index a479fb6ec43..ace7a59abdf 100644
--- a/pyrogram/api/core/object.py
+++ b/pyrogram/api/core/object.py
@@ -30,43 +30,51 @@ class Object:
QUALNAME = "Base"
@staticmethod
- def read(b: BytesIO, *args):
+ def read(b: BytesIO, *args): # TODO: Rename b -> data
return Object.all[int.from_bytes(b.read(4), "little")].read(b, *args)
def write(self, *args) -> bytes:
pass
+ def __eq__(self, other: "Object") -> bool:
+ for attr in self.__slots__:
+ try:
+ if getattr(self, attr) != getattr(other, attr):
+ return False
+ except AttributeError:
+ return False
+
+ return True
+
def __str__(self) -> str:
+ def default(obj: Object):
+ try:
+ return OrderedDict(
+ [("_", obj.QUALNAME)]
+ + [(attr, getattr(obj, attr))
+ for attr in obj.__slots__
+ if getattr(obj, attr) is not None]
+ )
+ except AttributeError:
+ if isinstance(obj, datetime):
+ return obj.strftime("%d-%b-%Y %H:%M:%S")
+ else:
+ return repr(obj)
+
return dumps(self, indent=4, default=default, ensure_ascii=False)
+ def __repr__(self) -> str:
+ return "pyrogram.api.{}({})".format(
+ self.QUALNAME,
+ ", ".join(
+ "{}={}".format(attr, repr(getattr(self, attr)))
+ for attr in self.__slots__
+ if getattr(self, attr) is not None
+ )
+ )
+
def __len__(self) -> int:
return len(self.write())
def __getitem__(self, item):
return getattr(self, item)
-
-
-def remove_none(obj):
- if isinstance(obj, (list, tuple, set)):
- return type(obj)(remove_none(x) for x in obj if x is not None)
- elif isinstance(obj, dict):
- return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
- else:
- return obj
-
-
-def default(o: "Object"):
- try:
- content = {i: getattr(o, i) for i in o.__slots__}
-
- return remove_none(
- OrderedDict(
- [("_", o.QUALNAME)]
- + [i for i in content.items()]
- )
- )
- except AttributeError:
- if isinstance(o, datetime):
- return o.strftime("%d-%b-%Y %H:%M:%S")
- else:
- return repr(o)
diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py
index ab546b5ead0..4d1c9a16df7 100644
--- a/pyrogram/client/types/inline_mode/inline_query.py
+++ b/pyrogram/client/types/inline_mode/inline_query.py
@@ -53,7 +53,7 @@ class InlineQuery(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: str,
from_user: User,
query: str,
@@ -62,7 +62,6 @@ def __init__(
):
super().__init__(client)
- self._client = client
self.id = id
self.from_user = from_user
self.query = query
diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py
index c9a46ff27c7..4b7f7ca3b1c 100644
--- a/pyrogram/client/types/inline_mode/inline_query_result.py
+++ b/pyrogram/client/types/inline_mode/inline_query_result.py
@@ -50,7 +50,7 @@ class InlineQueryResult(PyrogramType):
__slots__ = ["type", "id"]
def __init__(self, type: str, id: str):
- super().__init__(None)
+ super().__init__()
self.type = type
self.id = id
diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py
index 551ca639137..8360862f2a6 100644
--- a/pyrogram/client/types/input_media/input_media.py
+++ b/pyrogram/client/types/input_media/input_media.py
@@ -33,7 +33,7 @@ class InputMedia(PyrogramType):
__slots__ = ["media", "caption", "parse_mode"]
def __init__(self, media: str, caption: str, parse_mode: str):
- super().__init__(None)
+ super().__init__()
self.media = media
self.caption = caption
diff --git a/pyrogram/client/types/input_message_content/input_message_content.py b/pyrogram/client/types/input_message_content/input_message_content.py
index 0cd264b7ede..50e068b7e04 100644
--- a/pyrogram/client/types/input_message_content/input_message_content.py
+++ b/pyrogram/client/types/input_message_content/input_message_content.py
@@ -34,4 +34,4 @@ class InputMessageContent(PyrogramType):
__slots__ = []
def __init__(self):
- super().__init__(None)
+ super().__init__()
diff --git a/pyrogram/client/types/keyboards/callback_game.py b/pyrogram/client/types/keyboards/callback_game.py
index b7397075ae9..4fa43a30a59 100644
--- a/pyrogram/client/types/keyboards/callback_game.py
+++ b/pyrogram/client/types/keyboards/callback_game.py
@@ -28,4 +28,4 @@ class CallbackGame(PyrogramType):
__slots__ = []
def __init__(self):
- super().__init__(None)
+ super().__init__()
diff --git a/pyrogram/client/types/keyboards/callback_query.py b/pyrogram/client/types/keyboards/callback_query.py
index e58f77c2e58..822ea234e7d 100644
--- a/pyrogram/client/types/keyboards/callback_query.py
+++ b/pyrogram/client/types/keyboards/callback_query.py
@@ -64,7 +64,7 @@ class CallbackQuery(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: str,
from_user: User,
chat_instance: str,
diff --git a/pyrogram/client/types/keyboards/force_reply.py b/pyrogram/client/types/keyboards/force_reply.py
index f2d337b6758..5b263d0396a 100644
--- a/pyrogram/client/types/keyboards/force_reply.py
+++ b/pyrogram/client/types/keyboards/force_reply.py
@@ -42,7 +42,7 @@ def __init__(
self,
selective: bool = None
):
- super().__init__(None)
+ super().__init__()
self.selective = selective
diff --git a/pyrogram/client/types/keyboards/game_high_score.py b/pyrogram/client/types/keyboards/game_high_score.py
index 302dececdf8..56389b17279 100644
--- a/pyrogram/client/types/keyboards/game_high_score.py
+++ b/pyrogram/client/types/keyboards/game_high_score.py
@@ -42,7 +42,7 @@ class GameHighScore(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
user: User,
score: int,
position: int = None
diff --git a/pyrogram/client/types/keyboards/game_high_scores.py b/pyrogram/client/types/keyboards/game_high_scores.py
index 1c2cf105ba7..8183b9b01c8 100644
--- a/pyrogram/client/types/keyboards/game_high_scores.py
+++ b/pyrogram/client/types/keyboards/game_high_scores.py
@@ -40,7 +40,7 @@ class GameHighScores(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
total_count: int,
game_high_scores: List[GameHighScore]
):
diff --git a/pyrogram/client/types/keyboards/inline_keyboard_button.py b/pyrogram/client/types/keyboards/inline_keyboard_button.py
index 358eae21b03..7e9fd458162 100644
--- a/pyrogram/client/types/keyboards/inline_keyboard_button.py
+++ b/pyrogram/client/types/keyboards/inline_keyboard_button.py
@@ -71,7 +71,7 @@ def __init__(
switch_inline_query_current_chat: str = None,
callback_game: CallbackGame = None
):
- super().__init__(None)
+ super().__init__()
self.text = str(text)
self.url = url
diff --git a/pyrogram/client/types/keyboards/inline_keyboard_markup.py b/pyrogram/client/types/keyboards/inline_keyboard_markup.py
index c940fa1a21c..c7230eafd20 100644
--- a/pyrogram/client/types/keyboards/inline_keyboard_markup.py
+++ b/pyrogram/client/types/keyboards/inline_keyboard_markup.py
@@ -37,7 +37,7 @@ def __init__(
self,
inline_keyboard: List[List[InlineKeyboardButton]]
):
- super().__init__(None)
+ super().__init__()
self.inline_keyboard = inline_keyboard
diff --git a/pyrogram/client/types/keyboards/keyboard_button.py b/pyrogram/client/types/keyboards/keyboard_button.py
index 405e37b52e1..93d2e5ef6d4 100644
--- a/pyrogram/client/types/keyboards/keyboard_button.py
+++ b/pyrogram/client/types/keyboards/keyboard_button.py
@@ -48,7 +48,7 @@ def __init__(
request_contact: bool = None,
request_location: bool = None
):
- super().__init__(None)
+ super().__init__()
self.text = str(text)
self.request_contact = request_contact
diff --git a/pyrogram/client/types/keyboards/reply_keyboard_markup.py b/pyrogram/client/types/keyboards/reply_keyboard_markup.py
index 85ab16f46b7..c4b37b7c8ef 100644
--- a/pyrogram/client/types/keyboards/reply_keyboard_markup.py
+++ b/pyrogram/client/types/keyboards/reply_keyboard_markup.py
@@ -58,7 +58,7 @@ def __init__(
one_time_keyboard: bool = None,
selective: bool = None
):
- super().__init__(None)
+ super().__init__()
self.keyboard = keyboard
self.resize_keyboard = resize_keyboard
diff --git a/pyrogram/client/types/keyboards/reply_keyboard_remove.py b/pyrogram/client/types/keyboards/reply_keyboard_remove.py
index bb448447a0e..9d6eb7d511b 100644
--- a/pyrogram/client/types/keyboards/reply_keyboard_remove.py
+++ b/pyrogram/client/types/keyboards/reply_keyboard_remove.py
@@ -43,7 +43,7 @@ def __init__(
self,
selective: bool = None
):
- super().__init__(None)
+ super().__init__()
self.selective = selective
diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py
index 8d889cc2d37..cd6e03ab886 100644
--- a/pyrogram/client/types/messages_and_media/animation.py
+++ b/pyrogram/client/types/messages_and_media/animation.py
@@ -62,7 +62,7 @@ class Animation(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
width: int,
height: int,
diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py
index 704f4f75646..76181a2243c 100644
--- a/pyrogram/client/types/messages_and_media/audio.py
+++ b/pyrogram/client/types/messages_and_media/audio.py
@@ -62,7 +62,7 @@ class Audio(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
duration: int,
thumb: PhotoSize = None,
diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py
index fb4eb3a6eac..9205304edde 100644
--- a/pyrogram/client/types/messages_and_media/contact.py
+++ b/pyrogram/client/types/messages_and_media/contact.py
@@ -47,7 +47,7 @@ class Contact(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
phone_number: str,
first_name: str,
last_name: str = None,
diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py
index 754fc5af4b1..394d5e144e3 100644
--- a/pyrogram/client/types/messages_and_media/document.py
+++ b/pyrogram/client/types/messages_and_media/document.py
@@ -53,7 +53,7 @@ class Document(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
thumb: PhotoSize = None,
file_name: str = None,
diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py
index b4c96a11a57..1377173a551 100644
--- a/pyrogram/client/types/messages_and_media/game.py
+++ b/pyrogram/client/types/messages_and_media/game.py
@@ -53,7 +53,7 @@ class Game(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: int,
title: str,
short_name: str,
diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py
index c3d8f974a93..55def7a0f51 100644
--- a/pyrogram/client/types/messages_and_media/location.py
+++ b/pyrogram/client/types/messages_and_media/location.py
@@ -38,7 +38,7 @@ class Location(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
longitude: float,
latitude: float
):
diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py
index 28a5d0ea277..d37028b560d 100644
--- a/pyrogram/client/types/messages_and_media/message.py
+++ b/pyrogram/client/types/messages_and_media/message.py
@@ -282,7 +282,7 @@ class Message(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
message_id: int,
date: int = None,
chat: Chat = None,
@@ -2852,7 +2852,7 @@ def download(
block: bool = True,
progress: callable = None,
progress_args: tuple = ()
- ) -> "Message":
+ ) -> str:
"""Bound method *download* of :obj:`Message`.
Use as a shortcut for:
diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py
index 768dee1eecb..e369e74edc7 100644
--- a/pyrogram/client/types/messages_and_media/message_entity.py
+++ b/pyrogram/client/types/messages_and_media/message_entity.py
@@ -68,7 +68,7 @@ class MessageEntity(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
type: str,
offset: int,
length: int,
diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py
index 4f930a22630..379830be41d 100644
--- a/pyrogram/client/types/messages_and_media/messages.py
+++ b/pyrogram/client/types/messages_and_media/messages.py
@@ -42,7 +42,7 @@ class Messages(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
total_count: int,
messages: List[Message]
):
diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py
index c2d0eb1ff8e..8d60d59a733 100644
--- a/pyrogram/client/types/messages_and_media/photo.py
+++ b/pyrogram/client/types/messages_and_media/photo.py
@@ -46,7 +46,7 @@ class Photo(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: str,
date: int,
sizes: List[PhotoSize]
diff --git a/pyrogram/client/types/messages_and_media/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py
index c76ef914ec4..9f64ae6a45b 100644
--- a/pyrogram/client/types/messages_and_media/photo_size.py
+++ b/pyrogram/client/types/messages_and_media/photo_size.py
@@ -47,7 +47,7 @@ class PhotoSize(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
width: int,
height: int,
diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py
index 8aa32137148..e6c97bfb456 100644
--- a/pyrogram/client/types/messages_and_media/poll.py
+++ b/pyrogram/client/types/messages_and_media/poll.py
@@ -53,7 +53,7 @@ class Poll(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: str,
question: str,
options: List[PollOption],
diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py
index e594e3ca5d8..e7eb1f5f38d 100644
--- a/pyrogram/client/types/messages_and_media/poll_option.py
+++ b/pyrogram/client/types/messages_and_media/poll_option.py
@@ -30,14 +30,17 @@ class PollOption(PyrogramType):
voter_count (``int``):
Number of users that voted for this option.
Equals to 0 until you vote.
+
+ data (``bytes``):
+ The data this poll option is holding.
"""
- __slots__ = ["text", "voter_count", "_data"]
+ __slots__ = ["text", "voter_count", "data"]
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
text: str,
voter_count: int,
data: bytes
@@ -46,4 +49,4 @@ def __init__(
self.text = text
self.voter_count = voter_count
- self._data = data # Hidden
+ self.data = data
diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py
index 0a3a42d1999..edeee37ee49 100644
--- a/pyrogram/client/types/messages_and_media/sticker.py
+++ b/pyrogram/client/types/messages_and_media/sticker.py
@@ -71,7 +71,7 @@ class Sticker(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
width: int,
height: int,
diff --git a/pyrogram/client/types/messages_and_media/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py
index c74a371b2d1..628831d5a37 100644
--- a/pyrogram/client/types/messages_and_media/user_profile_photos.py
+++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py
@@ -39,7 +39,7 @@ class UserProfilePhotos(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
total_count: int,
photos: List[Photo]
):
diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py
index cac84e57807..e54a812e2ab 100644
--- a/pyrogram/client/types/messages_and_media/venue.py
+++ b/pyrogram/client/types/messages_and_media/venue.py
@@ -49,7 +49,7 @@ class Venue(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
location: Location,
title: str,
address: str,
diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py
index 13980270e58..529fc5ef2bb 100644
--- a/pyrogram/client/types/messages_and_media/video.py
+++ b/pyrogram/client/types/messages_and_media/video.py
@@ -68,7 +68,7 @@ class Video(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
width: int,
height: int,
diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py
index 5ebc8774cec..133ccae07fe 100644
--- a/pyrogram/client/types/messages_and_media/video_note.py
+++ b/pyrogram/client/types/messages_and_media/video_note.py
@@ -56,7 +56,7 @@ class VideoNote(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
length: int,
duration: int,
diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py
index 88154e2f5a1..3e08d57a5a3 100644
--- a/pyrogram/client/types/messages_and_media/voice.py
+++ b/pyrogram/client/types/messages_and_media/voice.py
@@ -52,7 +52,7 @@ class Voice(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
file_id: str,
duration: int,
waveform: bytes = None,
diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py
index 5f757d43321..ed50efbccce 100644
--- a/pyrogram/client/types/pyrogram_type.py
+++ b/pyrogram/client/types/pyrogram_type.py
@@ -25,34 +25,45 @@
class PyrogramType:
__slots__ = ["_client"]
- def __init__(self, client: "pyrogram.client.ext.BaseClient"):
+ def __init__(self, client: "pyrogram.BaseClient" = None):
self._client = client
- def __str__(self):
- return dumps(self, indent=4, default=default, ensure_ascii=False)
-
- def __getitem__(self, item):
- return getattr(self, item)
+ if self._client is None:
+ del self._client
+ def __eq__(self, other: "PyrogramType") -> bool:
+ for attr in self.__slots__:
+ try:
+ if getattr(self, attr) != getattr(other, attr):
+ return False
+ except AttributeError:
+ return False
-def remove_none(obj):
- if isinstance(obj, (list, tuple, set)):
- return type(obj)(remove_none(x) for x in obj if x is not None)
- elif isinstance(obj, dict):
- return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
- else:
- return obj
+ return True
+ def __str__(self) -> str:
+ def default(obj: PyrogramType):
+ try:
+ return OrderedDict(
+ [("_", "pyrogram." + obj.__class__.__name__)]
+ + [(attr, getattr(obj, attr))
+ for attr in obj.__slots__
+ if getattr(obj, attr) is not None]
+ )
+ except AttributeError:
+ return repr(obj)
-def default(o: PyrogramType):
- try:
- content = {i: getattr(o, i) for i in o.__slots__}
+ return dumps(self, indent=4, default=default, ensure_ascii=False)
- return remove_none(
- OrderedDict(
- [("_", "pyrogram." + o.__class__.__name__)]
- + [i for i in content.items() if not i[0].startswith("_")]
+ def __repr__(self) -> str:
+ return "pyrogram.{}({})".format(
+ self.__class__.__name__,
+ ", ".join(
+ "{}={}".format(attr, repr(getattr(self, attr)))
+ for attr in self.__slots__
+ if getattr(self, attr) is not None
)
)
- except AttributeError:
- return repr(o)
+
+ def __getitem__(self, item):
+ return getattr(self, item)
diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py
index 3b5f242b08d..8793942c5f9 100644
--- a/pyrogram/client/types/user_and_chats/chat.py
+++ b/pyrogram/client/types/user_and_chats/chat.py
@@ -89,7 +89,7 @@ class Chat(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: int,
type: str,
title: str = None,
diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py
index 9de9986a28f..536f95262f1 100644
--- a/pyrogram/client/types/user_and_chats/chat_member.py
+++ b/pyrogram/client/types/user_and_chats/chat_member.py
@@ -59,7 +59,7 @@ class ChatMember(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
user: "pyrogram.User",
status: str,
date: int = None,
diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py
index 8f98277ccec..f57b8b469e3 100644
--- a/pyrogram/client/types/user_and_chats/chat_members.py
+++ b/pyrogram/client/types/user_and_chats/chat_members.py
@@ -40,7 +40,7 @@ class ChatMembers(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
total_count: int,
chat_members: List[ChatMember]
):
diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py
index 3f1f7cc6543..37fde9fd3ea 100644
--- a/pyrogram/client/types/user_and_chats/chat_photo.py
+++ b/pyrogram/client/types/user_and_chats/chat_photo.py
@@ -40,7 +40,7 @@ class ChatPhoto(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
small_file_id: str,
big_file_id: str
):
diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py
index 880dcc8e363..0366d04ff40 100644
--- a/pyrogram/client/types/user_and_chats/chat_preview.py
+++ b/pyrogram/client/types/user_and_chats/chat_preview.py
@@ -50,7 +50,7 @@ class ChatPreview(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
title: str,
photo: ChatPhoto,
type: str,
diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py
index 3dc09dbc7b6..fc691ab64fc 100644
--- a/pyrogram/client/types/user_and_chats/dialog.py
+++ b/pyrogram/client/types/user_and_chats/dialog.py
@@ -51,7 +51,7 @@ class Dialog(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
chat: Chat,
top_message: "pyrogram.Message",
unread_messages_count: int,
diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py
index 43b9f66778e..0d6a093558e 100644
--- a/pyrogram/client/types/user_and_chats/dialogs.py
+++ b/pyrogram/client/types/user_and_chats/dialogs.py
@@ -41,7 +41,7 @@ class Dialogs(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
total_count: int,
dialogs: List[Dialog]
):
diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py
index c5a0976c094..455b4a4d4f9 100644
--- a/pyrogram/client/types/user_and_chats/user.py
+++ b/pyrogram/client/types/user_and_chats/user.py
@@ -78,7 +78,7 @@ class User(PyrogramType):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
id: int,
is_self: bool,
is_contact: bool,
diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py
index f91c2924586..e6f5b1343b1 100644
--- a/pyrogram/client/types/user_and_chats/user_status.py
+++ b/pyrogram/client/types/user_and_chats/user_status.py
@@ -70,7 +70,7 @@ class UserStatus(PyrogramType, Update):
def __init__(
self,
*,
- client: "pyrogram.client.ext.BaseClient",
+ client: "pyrogram.BaseClient" = None,
user_id: int,
online: bool = None,
offline: bool = None,
From 90115448ac39794b22c250b49873f6c7c15d35f0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 16:37:26 +0200
Subject: [PATCH 0135/1652] Update link hint for No API Key found errors
---
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 f65ef231d0f..31a7c14cf3c 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -1093,7 +1093,7 @@ def load_config(self):
else:
raise AttributeError(
"No API Key found. "
- "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
+ "More info: https://docs.pyrogram.ml/intro/setup#configuration"
)
for option in ["app_version", "device_model", "system_version", "lang_code"]:
From fb014f315ce82210807153e670461ae99c55554e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 17:15:00 +0200
Subject: [PATCH 0136/1652] Update and rename README.rst to README.md
---
README.md | 94 ++++++++++++++++++++++++++++++++++++++
README.rst | 131 -----------------------------------------------------
2 files changed, 94 insertions(+), 131 deletions(-)
create mode 100644 README.md
delete mode 100644 README.rst
diff --git a/README.md b/README.md
new file mode 100644
index 00000000000..35c80f37c44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,94 @@
+
+
+
+
+
+ Telegram MTProto API Framework for Python
+
+
+ Documentation
+
+ •
+
+ Releases
+
+ •
+
+ Community
+
+
+
+
+
+
+
+
+
+
+## Pyrogram
+
+``` python
+from pyrogram import Client, Filters
+
+app = Client("my_account")
+
+
+@app.on_message(Filters.private)
+def hello(client, message):
+ message.reply("Hello {}".format(message.from_user.first_name))
+
+
+app.run()
+```
+
+**Pyrogram** is an elegant, easy-to-use [Telegram](https://telegram.org/) client library and framework written from the
+ground up in Python and C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the [MTProto API](https://core.telegram.org/api#telegram-api).
+
+> [Pyrogram in fully-asynchronous mode is also available »](https://github.com/pyrogram/pyrogram/issues/181)
+>
+> [Working PoC of Telegram voice calls using Pyrogram »](https://github.com/bakatrouble/pytgvoip)
+
+### Features
+
+- **Easy**: You can install Pyrogram with pip and start building your applications right away.
+- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
+- **Fast**: Crypto parts are boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance library
+ written in pure C.
+- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
+- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
+- **Updated**, to the latest Telegram API version, currently Layer 97 on top of
+ [MTProto 2.0](https://core.telegram.org/mtproto).
+- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
+- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
+
+### Requirements
+
+- Python 3.4 or higher.
+- A [Telegram API key](https://docs.pyrogram.ml/intro/setup#api-keys).
+
+### Installing
+
+``` bash
+pip3 install pyrogram
+```
+
+### Resources
+
+- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
+- Reading [Examples in this repository](https://github.com/pyrogram/pyrogram/tree/master/examples) is also a good way
+ for learning how Pyrogram works.
+- Seeking extra help? Don't be shy, come join and ask our [Community](https://t.me/PyrogramChat)!
+- For other requests you can send an [Email](mailto:admin@pyrogram.ml) or a [Message](https://t.me/haskell).
+
+### Contributing
+
+Pyrogram is brand new, and **you are welcome to try it and help make it even better** by either submitting pull
+requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
+and documentation. Any help is appreciated!
+
+### Copyright & License
+
+- Copyright (C) 2017-2019 Dan Tès <>
+- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser)
diff --git a/README.rst b/README.rst
deleted file mode 100644
index dfb03abcf45..00000000000
--- a/README.rst
+++ /dev/null
@@ -1,131 +0,0 @@
-|header|
-
-Pyrogram
-========
-
-.. code-block:: python
-
- from pyrogram import Client, Filters
-
- app = Client("my_account")
-
-
- @app.on_message(Filters.private)
- def hello(client, message):
- message.reply("Hello {}".format(message.from_user.first_name))
-
-
- app.run()
-
-**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and C.
-It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the `MTProto API`_.
-
- `Pyrogram in fully-asynchronous mode is also available » `_
-
- `Working PoC of Telegram voice calls using Pyrogram » `_
-
-Features
---------
-
-- **Easy**: You can install Pyrogram with pip and start building your applications right away.
-- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
-- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
-- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
-- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
-- **Updated**, to the latest Telegram API version, currently Layer 97 on top of `MTProto 2.0`_.
-- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
-- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
-
-Requirements
-------------
-
-- Python 3.4 or higher.
-- A `Telegram API key`_.
-
-Installing
-----------
-
-.. code:: shell
-
- pip3 install pyrogram
-
-Resources
----------
-
-- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
-- Reading `Examples in this repository`_ is also a good way for learning how Pyrogram works.
-- Seeking extra help? Don't be shy, come join and ask our Community_!
-- For other requests you can send an Email_ or a Message_.
-
-Contributing
-------------
-
-Pyrogram is brand new, and **you are welcome to try it and help make it even better** by either submitting pull
-requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
-and documentation. Any help is appreciated!
-
-Copyright & License
--------------------
-
-- Copyright (C) 2017-2019 Dan Tès
-- Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_
-
-.. _`Telegram`: https://telegram.org/
-.. _`MTProto API`: https://core.telegram.org/api#telegram-api
-.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
-.. _`Community`: https://t.me/PyrogramChat
-.. _`Examples in this repository`: https://github.com/pyrogram/pyrogram/tree/master/examples
-.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
-.. _`Email`: admin@pyrogram.ml
-.. _`Message`: https://t.me/haskell
-.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-.. _`MTProto 2.0`: https://core.telegram.org/mtproto
-.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
-
-.. |header| raw:: html
-
-
-
-
- Telegram MTProto API Framework for Python
-
-
-
- Documentation
-
- •
-
- Changelog
-
- •
-
- Community
-
-
-
-
-
-
-
-
-
-
-.. |logo| image:: https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png
- :target: https://pyrogram.ml
- :alt: Pyrogram
-
-.. |description| replace:: **Telegram MTProto API Framework for Python**
-
-.. |schema| image:: https://img.shields.io/badge/schema-layer%2097-eda738.svg?longCache=true&colorA=262b30
- :target: compiler/api/source/main_api.tl
- :alt: Schema Layer
-
-.. |tgcrypto| image:: https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30
- :target: https://github.com/pyrogram/tgcrypto
- :alt: TgCrypto Version
From 9a2602ff2a5704efdaeaa85b5f672f1964b845d7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 17:17:20 +0200
Subject: [PATCH 0137/1652] Update setup.py
---
setup.py | 38 ++++++++++++++++----------------------
1 file changed, 16 insertions(+), 22 deletions(-)
diff --git a/setup.py b/setup.py
index 437ba39a49f..245655e690a 100644
--- a/setup.py
+++ b/setup.py
@@ -27,22 +27,14 @@
from compiler.docs import compiler as docs_compiler
from compiler.error import compiler as error_compiler
+with open("requirements.txt", encoding="utf-8") as r:
+ requires = [i.strip() for i in r]
-def read(file: str) -> list:
- with open(file, encoding="utf-8") as r:
- return [i.strip() for i in r]
+with open("pyrogram/__init__.py", encoding="utf-8") as f:
+ version = re.findall(r"__version__ = \"(.+)\"", f.read())[0]
-
-def get_version():
- with open("pyrogram/__init__.py", encoding="utf-8") as f:
- return re.findall(r"__version__ = \"(.+)\"", f.read())[0]
-
-
-def get_readme():
- # PyPI doesn't like raw html
- with open("README.rst", encoding="utf-8") as f:
- readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read())
- return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|schema| |tgcrypto|", readme)
+with open("README.md", encoding="utf-8") as f:
+ readme = f.read()
class Clean(Command):
@@ -128,23 +120,24 @@ def run(self):
if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]:
- error_compiler.start()
api_compiler.start()
- docs_compiler.start()
+ error_compiler.start()
setup(
name="Pyrogram",
- version=get_version(),
+ version=version,
description="Telegram MTProto API Client Library for Python",
- long_description=get_readme(),
+ long_description=readme,
+ long_description_content_type="text/markdown",
url="https://github.com/pyrogram",
download_url="https://github.com/pyrogram/pyrogram/releases/latest",
author="Dan Tès",
author_email="admin@pyrogram.ml",
license="LGPLv3+",
classifiers=[
- "Development Status :: 3 - Alpha",
+ "Development Status :: 4 - Beta",
"Intended Audience :: Developers",
+ "Natural Language :: English",
"License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
"Operating System :: OS Independent",
"Programming Language :: Python",
@@ -152,6 +145,8 @@ def run(self):
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
@@ -175,10 +170,9 @@ def run(self):
"pyrogram.client.ext": ["mime.types"]
},
zip_safe=False,
- install_requires=read("requirements.txt"),
+ install_requires=requires,
extras_require={
- "tgcrypto": ["tgcrypto==1.1.1"], # TODO: Remove soon
- "fast": ["tgcrypto==1.1.1"],
+ "fast": ["tgcrypto==1.1.1"]
},
cmdclass={
"clean": Clean,
From 29ac51f256286c610954d7b955c0c08bcfbe59b6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 17:21:15 +0200
Subject: [PATCH 0138/1652] Include README.md into MANIFEST.in
---
MANIFEST.in | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MANIFEST.in b/MANIFEST.in
index 80c061ff473..97d045881e5 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,5 @@
## Include
-include COPYING COPYING.lesser NOTICE requirements.txt
+include README.md COPYING COPYING.lesser NOTICE requirements.txt
recursive-include compiler *.py *.tl *.tsv *.txt
recursive-include pyrogram mime.types
From 0914654ba69b9cf013f286e95315b69c57060b8c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 18:04:44 +0200
Subject: [PATCH 0139/1652] Fix cross-reference links
---
docs/source/index.rst | 13 +++++++------
docs/source/intro/install.rst | 2 +-
docs/source/start/errors.rst | 20 ++++++++++----------
docs/source/start/updates.rst | 5 ++++-
docs/source/topics/advanced-usage.rst | 13 +++++--------
docs/source/topics/config-file.rst | 6 +++---
docs/source/topics/faq.rst | 4 ++--
docs/source/topics/more-on-updates.rst | 4 ++--
docs/source/topics/session-settings.rst | 4 ++--
docs/source/topics/smart-plugins.rst | 4 ++--
10 files changed, 38 insertions(+), 37 deletions(-)
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 81b4213ce18..9ff0f720754 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -23,7 +23,7 @@ Welcome to Pyrogram
•
- Changelog
+ Releases
•
@@ -55,7 +55,7 @@ Welcome to Pyrogram
app.run()
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and
-C. It enables you to easily create custom apps using both user and bot identities (bot API alternative) via the
+C. It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the
`MTProto API`_.
.. _Telegram: https://telegram.org
@@ -65,7 +65,8 @@ How the Documentation is Organized
----------------------------------
Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in
-order using the Next button at the end of each page. Here below you can find a list of the most relevant pages.
+order using the Next button at the end of each page. Here below you can, instead, find a list of the most relevant
+pages.
Getting Started
^^^^^^^^^^^^^^^
@@ -86,9 +87,9 @@ API Reference
- `Available Methods`_ - A list of available high-level methods.
- `Available Types`_ - A list of available high-level types.
-.. _Client Class: core/client
-.. _Available Methods: core/methods
-.. _Available Types: core/types
+.. _Client Class: api/client
+.. _Available Methods: api/methods
+.. _Available Types: api/types
Topics
^^^^^^
diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst
index 161c7d61e82..e2e4365a0f4 100644
--- a/docs/source/intro/install.rst
+++ b/docs/source/intro/install.rst
@@ -88,5 +88,5 @@ If no error shows up you are good to go.
>>> pyrogram.__version__
'0.12.0'
-.. _TgCrypto: ../resources/tgcrypto.html
+.. _TgCrypto: ../topics/tgcrypto
.. _`Github repo`: http://github.com/pyrogram/pyrogram
diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst
index d05206c91ad..1260672e06a 100644
--- a/docs/source/start/errors.rst
+++ b/docs/source/start/errors.rst
@@ -4,18 +4,18 @@ Error Handling
Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks.
There are many errors that Telegram could return, but they all fall in one of these categories
-(which are in turn children of the :obj:`RPCError ` superclass):
+(which are in turn children of the ``RPCError`` superclass):
-- :obj:`303 - See Other `
-- :obj:`400 - Bad Request `
-- :obj:`401 - Unauthorized `
-- :obj:`403 - Forbidden `
-- :obj:`406 - Not Acceptable `
-- :obj:`420 - Flood `
-- :obj:`500 - Internal Server Error `
+- `303 - See Other <../api/errors#see-other>`_
+- `400 - Bad Request <../api/errors#bad-request>`_
+- `401 - Unauthorized <../api/errors#unauthorized>`_
+- `403 - Forbidden <../api/errors#forbidden>`_
+- `406 - Not Acceptable <../api/errors#not-acceptable>`_
+- `420 - Flood <../api/errors#flood>`_
+- `500 - Internal Server Error <../api/errors#internal-server-error>`_
As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a
-specific one, it raises a special :obj:`520 Unknown Error ` exception and logs it
+specific one, it raises a special ``520 Unknown Error`` exception and logs it
in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors.
Examples
@@ -44,7 +44,7 @@ Examples
pass
Exception objects may also contain some informative values.
-E.g.: :obj:`FloodWait ` holds the amount of seconds you have to wait
+E.g.: ``FloodWait`` holds the amount of seconds you have to wait
before you can try again. The value is always stored in the ``x`` field of the returned exception object:
.. code-block:: python
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index 0dcb08ad82b..85005b9c712 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -4,10 +4,13 @@ Handling Updates
Calling `API methods`_ sequentially is cool, but how to react when, for example, a new message arrives? This page deals
with updates and how to handle such events in Pyrogram. Let's have a look at how they work.
+Defining Updates
+----------------
+
First, let's define what are these updates. As hinted already, updates are simply events that happen in your Telegram
account (incoming messages, new members join, button presses, etc...), which are meant to notify you about a new
specific state that changed. These updates are handled by registering one or more callback functions in your app using
-`Handlers <../pyrogram/Handlers.html>`_.
+`Handlers <../api/handlers>`_.
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
function will be called and its body executed.
diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst
index 8b722b2a9e5..970e07e0496 100644
--- a/docs/source/topics/advanced-usage.rst
+++ b/docs/source/topics/advanced-usage.rst
@@ -125,12 +125,9 @@ For example, given the ID *123456789*, here's how Pyrogram can tell entities apa
So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an
high-level method.
-
-
-
-.. _methods: ../pyrogram/Client.html#messages
-.. _types: ../pyrogram/Types.html
-.. _plenty of them: ../pyrogram/Client.html#messages
-.. _raw functions: ../pyrogram/functions
-.. _raw types: ../pyrogram/types
+.. _methods: ../api/methods
+.. _types: ../api/types
+.. _plenty of them: ../api/methods
+.. _raw functions: ../telegram/functions
+.. _raw types: ../telegram/types
.. _Community: https://t.me/PyrogramChat
\ No newline at end of file
diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst
index 2a50277fde6..14ae9fb6946 100644
--- a/docs/source/topics/config-file.rst
+++ b/docs/source/topics/config-file.rst
@@ -54,7 +54,7 @@ The ``[pyrogram]`` section contains your Telegram API credentials: *api_id* and
api_id = 12345
api_hash = 0123456789abcdef0123456789abcdef
-`More info about API Key. <../start/Setup.html#configuration>`_
+`More info about API Key. <../intro/setup#api-keys>`_
Proxy
^^^^^
@@ -70,7 +70,7 @@ The ``[proxy]`` section contains settings about your SOCKS5 proxy.
username =
password =
-`More info about SOCKS5 Proxy. `_
+`More info about SOCKS5 Proxy. `_
Plugins
^^^^^^^
@@ -87,4 +87,4 @@ The ``[plugins]`` section contains settings about Smart Plugins.
exclude =
module fn2
-`More info about Smart Plugins. `_
+`More info about Smart Plugins. `_
diff --git a/docs/source/topics/faq.rst b/docs/source/topics/faq.rst
index 5a8a39293f5..0834389ea2a 100644
--- a/docs/source/topics/faq.rst
+++ b/docs/source/topics/faq.rst
@@ -11,7 +11,7 @@ What is Pyrogram?
-----------------
**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and
-C. It enables you to easily create custom applications using both user and bot identities (bot API alternative) via the
+C. It enables you to easily create custom applications for both user and bot identities (bot API alternative) via the
`MTProto API`_ with the Python programming language.
.. _Telegram: https://telegram.org
@@ -30,7 +30,7 @@ How old is Pyrogram?
Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior the
initial public release on `GitHub`_.
-.. _GitHub:
+.. _GitHub: https://github.com/pyrogram/pyrogram
Why do I need an API key for bots?
----------------------------------
diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst
index 8408662260e..cb319ee1cb3 100644
--- a/docs/source/topics/more-on-updates.rst
+++ b/docs/source/topics/more-on-updates.rst
@@ -218,5 +218,5 @@ The output of both (equivalent) examples will be:
1
2
-.. _`update handlers`: UpdateHandling.html
-.. _`filters`: UsingFilters.html
\ No newline at end of file
+.. _`update handlers`: ../start/updates
+.. _`filters`: filters
\ No newline at end of file
diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/session-settings.rst
index 47c6872e9c1..89b7e62c7c0 100644
--- a/docs/source/topics/session-settings.rst
+++ b/docs/source/topics/session-settings.rst
@@ -5,8 +5,8 @@ As you may probably know, Telegram allows users (and bots) having more than one
in the system at the same time.
Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official
-app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ with Pyrogram). They store some
-useful information such as the client who's using them and from which country and IP address.
+app (or by invoking `GetAuthorizations <../telegram/functions/account/GetAuthorizations.html>`_ with Pyrogram). They
+store some useful information such as the client who's using them and from which country and IP address.
.. figure:: https://i.imgur.com/lzGPCdZ.png
:width: 70%
diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst
index 6f266590ecd..fbe2738893f 100644
--- a/docs/source/topics/smart-plugins.rst
+++ b/docs/source/topics/smart-plugins.rst
@@ -65,7 +65,7 @@ after importing your modules, like this:
app.run()
This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
-manually ``import``, manually :meth:`add_handler ` and manually instantiate each
+manually ``import``, manually :meth:`add_handler() ` and manually instantiate each
:obj:`MessageHandler ` object because **you can't use those cool decorators** for your
functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically.
@@ -156,7 +156,7 @@ found inside each module will be, instead, loaded in the order they are defined,
.. note::
Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping
- filters included a second time will not work. Learn more at `More on Updates `_.
+ filters included a second time will not work. Learn more at `More on Updates `_.
This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or
exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude``
From 94de75f7144be9f69d5eb58107c84098c3568238 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 21:03:55 +0200
Subject: [PATCH 0140/1652] Bring back the possibility to use strings as
callback query data In case bytes (which is the type used by telegram) can't
be successfully decoded into strings, the raw bytes are presented instead of
trying to decode by ignoring/replacing errors.
---
.../methods/bots/request_callback_answer.py | 10 +++++++---
.../client/types/keyboards/callback_query.py | 16 ++++++++++++----
.../types/keyboards/inline_keyboard_button.py | 17 +++++++++++++----
3 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py
index 443cb825472..97d8d42bfe5 100644
--- a/pyrogram/client/methods/bots/request_callback_answer.py
+++ b/pyrogram/client/methods/bots/request_callback_answer.py
@@ -27,7 +27,7 @@ def request_callback_answer(
self,
chat_id: Union[int, str],
message_id: int,
- callback_data: bytes,
+ callback_data: Union[str, bytes],
timeout: int = 10
):
"""Request a callback answer from bots.
@@ -42,7 +42,7 @@ def request_callback_answer(
message_id (``int``):
The message id the inline keyboard is attached on.
- callback_data (``bytes``):
+ callback_data (``str`` | ``bytes``):
Callback data associated with the inline button you want to get the answer from.
timeout (``int``, *optional*):
@@ -56,11 +56,15 @@ def request_callback_answer(
RPCError: In case of a Telegram RPC error.
TimeoutError: In case the bot fails to answer within 10 seconds.
"""
+
+ # Telegram only wants bytes, but we are allowed to pass strings too.
+ data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data
+
return self.send(
functions.messages.GetBotCallbackAnswer(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
- data=callback_data
+ data=data
),
retries=0,
timeout=timeout
diff --git a/pyrogram/client/types/keyboards/callback_query.py b/pyrogram/client/types/keyboards/callback_query.py
index 822ea234e7d..4d657767a76 100644
--- a/pyrogram/client/types/keyboards/callback_query.py
+++ b/pyrogram/client/types/keyboards/callback_query.py
@@ -18,6 +18,7 @@
from base64 import b64encode
from struct import pack
+from typing import Union
import pyrogram
from pyrogram.api import types
@@ -51,7 +52,7 @@ class CallbackQuery(PyrogramType, Update):
inline_message_id (``str``):
Identifier of the message sent via the bot in inline mode, that originated the query.
- data (``bytes``, *optional*):
+ data (``str`` | ``bytes``, *optional*):
Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field.
game_short_name (``str``, *optional*):
@@ -70,7 +71,7 @@ def __init__(
chat_instance: str,
message: "pyrogram.Message" = None,
inline_message_id: str = None,
- data: bytes = None,
+ data: Union[str, bytes] = None,
game_short_name: str = None
):
super().__init__(client)
@@ -80,7 +81,7 @@ def __init__(
self.chat_instance = chat_instance
self.message = message
self.inline_message_id = inline_message_id
- self.data = str(data, "utf-8")
+ self.data = data
self.game_short_name = game_short_name
@staticmethod
@@ -110,13 +111,20 @@ def _parse(client, callback_query, users) -> "CallbackQuery":
b"-_"
).decode().rstrip("=")
+ # Try to decode callback query data into string. If that fails, fallback to bytes instead of decoding by
+ # ignoring/replacing errors, this way, button clicks will still work.
+ try:
+ data = callback_query.data.decode()
+ except UnicodeDecodeError:
+ data = callback_query.data
+
return CallbackQuery(
id=str(callback_query.query_id),
from_user=User._parse(client, users[callback_query.user_id]),
message=message,
inline_message_id=inline_message_id,
chat_instance=str(callback_query.chat_instance),
- data=callback_query.data,
+ data=data,
game_short_name=callback_query.game_short_name,
client=client
)
diff --git a/pyrogram/client/types/keyboards/inline_keyboard_button.py b/pyrogram/client/types/keyboards/inline_keyboard_button.py
index 7e9fd458162..08ad0f35762 100644
--- a/pyrogram/client/types/keyboards/inline_keyboard_button.py
+++ b/pyrogram/client/types/keyboards/inline_keyboard_button.py
@@ -35,7 +35,7 @@ class InlineKeyboardButton(PyrogramType):
text (``str``):
Label text on the button.
- callback_data (``bytes``, *optional*):
+ callback_data (``str`` | ``bytes``, *optional*):
Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes.
url (``str``, *optional*):
@@ -75,7 +75,7 @@ def __init__(
self.text = str(text)
self.url = url
- self.callback_data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data
+ self.callback_data = 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
@@ -90,9 +90,16 @@ def read(o):
)
if isinstance(o, KeyboardButtonCallback):
+ # Try decode data to keep it as string, but if fails, fallback to bytes so we don't lose any information,
+ # instead of decoding by ignoring/replacing errors.
+ try:
+ data = o.data.decode()
+ except UnicodeDecodeError:
+ data = o.data
+
return InlineKeyboardButton(
text=o.text,
- callback_data=o.data
+ callback_data=data
)
if isinstance(o, KeyboardButtonSwitchInline):
@@ -115,7 +122,9 @@ def read(o):
def write(self):
if self.callback_data is not None:
- return KeyboardButtonCallback(text=self.text, data=self.callback_data)
+ # Telegram only wants bytes, but we are allowed to pass strings too, for convenience.
+ data = bytes(self.callback_data, "utf-8") if isinstance(self.callback_data, str) else self.callback_data
+ return KeyboardButtonCallback(text=self.text, data=data)
if self.url is not None:
return KeyboardButtonUrl(text=self.text, url=self.url)
From a5e42572f62986093fc8f64aed316a248234dc35 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 13 May 2019 21:05:47 +0200
Subject: [PATCH 0141/1652] Allow Message.click() without arguments. Default to
0 (first button)
---
pyrogram/client/types/messages_and_media/message.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py
index d37028b560d..a8f6d31be26 100644
--- a/pyrogram/client/types/messages_and_media/message.py
+++ b/pyrogram/client/types/messages_and_media/message.py
@@ -2725,7 +2725,7 @@ def delete(self, revoke: bool = True):
revoke=revoke
)
- def click(self, x: int or str, y: int = None, quote: bool = None, timeout: int = 10):
+ def click(self, x: int or str, y: int = 0, quote: bool = None, timeout: int = 10):
"""Bound method *click* of :obj:`Message`.
Use as a shortcut for clicking a button attached to the message instead of:
From 944b672fe5b044446970887b868e9785addb6525 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 16 May 2019 21:28:34 +0200
Subject: [PATCH 0142/1652] Docs revamp. Part 4
---
compiler/docs/template/page.txt | 2 +-
docs/source/api/bound-methods.rst | 102 ++++++++++++++++++
docs/source/api/client.rst | 13 ++-
docs/source/api/decorators.rst | 7 +-
docs/source/api/errors.rst | 23 ++--
docs/source/api/handlers.rst | 9 +-
docs/source/api/methods.rst | 5 +-
docs/source/api/types.rst | 98 +----------------
docs/source/index.rst | 12 ++-
docs/source/intro/auth.rst | 1 +
docs/source/intro/install.rst | 13 +--
docs/source/intro/start.rst | 7 +-
docs/source/start/errors.rst | 80 +++++++-------
docs/source/start/invoking.rst | 9 +-
docs/source/start/updates.rst | 4 +-
docs/source/topics/advanced-usage.rst | 6 +-
docs/source/topics/faq.rst | 58 ++++++++--
docs/source/topics/filters.rst | 3 +
docs/source/topics/glossary.rst | 53 +++++++++
docs/source/topics/smart-plugins.rst | 26 ++---
pyrogram/client/client.py | 18 ++--
pyrogram/client/filters/filters.py | 34 ++----
.../methods/decorators/on_callback_query.py | 3 +-
.../methods/decorators/on_deleted_messages.py | 3 +-
.../methods/decorators/on_disconnect.py | 3 +-
.../methods/decorators/on_inline_query.py | 3 +-
.../client/methods/decorators/on_message.py | 3 +-
pyrogram/client/methods/decorators/on_poll.py | 3 +-
.../methods/decorators/on_raw_update.py | 3 +-
.../methods/decorators/on_user_status.py | 2 +-
.../methods/messages/forward_messages.py | 2 +-
.../client/methods/messages/send_document.py | 2 +-
.../client/types/keyboards/callback_query.py | 6 +-
.../types/messages_and_media/message.py | 55 +++++-----
.../client/types/user_and_chats/chat_photo.py | 2 +-
35 files changed, 398 insertions(+), 275 deletions(-)
create mode 100644 docs/source/api/bound-methods.rst
create mode 100644 docs/source/topics/glossary.rst
diff --git a/compiler/docs/template/page.txt b/compiler/docs/template/page.txt
index 25a396fae6e..638a10cf118 100644
--- a/compiler/docs/template/page.txt
+++ b/compiler/docs/template/page.txt
@@ -1,5 +1,5 @@
{title}
{title_markup}
-.. autoclass:: {full_class_path}
+.. autoclass:: {full_class_path}()
:members:
diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst
new file mode 100644
index 00000000000..d93497fe37e
--- /dev/null
+++ b/docs/source/api/bound-methods.rst
@@ -0,0 +1,102 @@
+Bound Methods
+=============
+
+Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are
+accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring
+some of the required arguments.
+
+.. code-block:: python
+ :emphasize-lines: 8
+
+ from pyrogram import Client
+
+ app = Client("my_account")
+
+
+ @app.on_message()
+ def hello(client, message)
+ message.reply("hi")
+
+
+ app.run()
+
+.. currentmodule:: pyrogram
+
+- Message_
+- CallbackQuery_
+- InlineQuery_
+
+.. _Message:
+
+Message
+-------
+
+- :meth:`Message.click()`
+- :meth:`Message.delete()`
+- :meth:`Message.download()`
+- :meth:`Message.edit()`
+- :meth:`Message.edit_caption()`
+- :meth:`Message.edit_media()`
+- :meth:`Message.edit_reply_markup()`
+- :meth:`Message.forward()`
+- :meth:`Message.pin()`
+- :meth:`Message.reply()`
+- :meth:`Message.reply_animation()`
+- :meth:`Message.reply_audio()`
+- :meth:`Message.reply_cached_media()`
+- :meth:`Message.reply_chat_action()`
+- :meth:`Message.reply_contact()`
+- :meth:`Message.reply_document()`
+- :meth:`Message.reply_game()`
+- :meth:`Message.reply_inline_bot_result()`
+- :meth:`Message.reply_location()`
+- :meth:`Message.reply_media_group()`
+- :meth:`Message.reply_photo()`
+- :meth:`Message.reply_poll()`
+- :meth:`Message.reply_sticker()`
+- :meth:`Message.reply_venue()`
+- :meth:`Message.reply_video()`
+- :meth:`Message.reply_video_note()`
+- :meth:`Message.reply_voice()`
+
+.. automethod:: Message.click()
+.. automethod:: Message.delete()
+.. automethod:: Message.download()
+.. automethod:: Message.edit()
+.. automethod:: Message.edit_caption()
+.. automethod:: Message.edit_media()
+.. automethod:: Message.edit_reply_markup()
+.. automethod:: Message.forward()
+.. automethod:: Message.pin()
+.. automethod:: Message.reply()
+.. automethod:: Message.reply_animation()
+.. automethod:: Message.reply_audio()
+.. automethod:: Message.reply_cached_media()
+.. automethod:: Message.reply_chat_action()
+.. automethod:: Message.reply_contact()
+.. automethod:: Message.reply_document()
+.. automethod:: Message.reply_game()
+.. automethod:: Message.reply_inline_bot_result()
+.. automethod:: Message.reply_location()
+.. automethod:: Message.reply_media_group()
+.. automethod:: Message.reply_photo()
+.. automethod:: Message.reply_poll()
+.. automethod:: Message.reply_sticker()
+.. automethod:: Message.reply_venue()
+.. automethod:: Message.reply_video()
+.. automethod:: Message.reply_video_note()
+.. automethod:: Message.reply_voice()
+
+.. _CallbackQuery:
+
+CallbackQuery
+-------------
+
+.. automethod:: CallbackQuery.answer()
+
+.. _InlineQuery:
+
+InlineQuery
+-----------
+
+.. automethod:: InlineQuery.answer()
\ No newline at end of file
diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst
index 05c5cd0ce72..01e3f5f8cdd 100644
--- a/docs/source/api/client.rst
+++ b/docs/source/api/client.rst
@@ -1,7 +1,16 @@
Pyrogram Client
===============
-The :class:`Client ` is the main class. It exposes easy-to-use methods that are named
-after the well established Telegram Bot API methods, thus offering a familiar look to Bot developers.
+This class exposes high-level methods for an easy access to the API.
+
+.. code-block:: python
+ :emphasize-lines: 1-3
+
+ from pyrogram import Client
+
+ app = Client("my_account")
+
+ with app:
+ app.send_message("me", "Hi!")
.. autoclass:: pyrogram.Client()
diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst
index a9cd70c7cc5..26bd64e1ccb 100644
--- a/docs/source/api/decorators.rst
+++ b/docs/source/api/decorators.rst
@@ -1,7 +1,7 @@
Decorators
==========
-While still being methods bound to the :obj:`Client` class, decorators are of a special kind and thus deserve a
+While still being methods bound to the :obj:`Client ` class, decorators are of a special kind and thus deserve a
dedicated page.
Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to
@@ -9,13 +9,12 @@ Decorators are able to register callback functions for handling updates in a muc
:meth:`add_handler() `, automatically. All you need to do is adding the decorators on top
of your functions.
-**Example:**
-
.. code-block:: python
+ :emphasize-lines: 6
from pyrogram import Client
- app = Client(...)
+ app = Client("my_account")
@app.on_message()
diff --git a/docs/source/api/errors.rst b/docs/source/api/errors.rst
index 29dcaface35..fad571e3e2e 100644
--- a/docs/source/api/errors.rst
+++ b/docs/source/api/errors.rst
@@ -5,27 +5,26 @@ All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.error
The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram
follow the usual *PascalCase* convention.
-**Example**:
-
.. code-block:: python
+ :emphasize-lines: 1, 5
- from pyrogram.errors import InternalServerError
+ from pyrogram.errors import FloodWait
try:
...
- except FloodWait:
+ except FloodWait as e:
...
-303 - See Other
----------------
+303 - SeeOther
+--------------
.. csv-table::
:file: ../../../compiler/error/source/303_SEE_OTHER.tsv
:delim: tab
:header-rows: 1
-400 - Bad Request
------------------
+400 - BadRequest
+----------------
.. csv-table::
:file: ../../../compiler/error/source/400_BAD_REQUEST.tsv
@@ -48,8 +47,8 @@ follow the usual *PascalCase* convention.
:delim: tab
:header-rows: 1
-406 - Not Acceptable
---------------------
+406 - NotAcceptable
+-------------------
.. csv-table::
:file: ../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv
@@ -64,8 +63,8 @@ follow the usual *PascalCase* convention.
:delim: tab
:header-rows: 1
-500 - Internal Server Error
----------------------------
+500 - InternalServerError
+-------------------------
.. csv-table::
:file: ../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst
index 08d40039a95..023f6e28b53 100644
--- a/docs/source/api/handlers.rst
+++ b/docs/source/api/handlers.rst
@@ -7,13 +7,12 @@ For a much more convenient way of registering callback functions have a look at
In case you decided to manually create an handler, use :meth:`add_handler() ` to register
it.
-**Example:**
-
.. code-block:: python
+ :emphasize-lines: 1, 10
from pyrogram import Client, MessageHandler
- app = Client(...)
+ app = Client("my_account")
def dump(client, message):
@@ -34,6 +33,7 @@ it.
CallbackQueryHandler
InlineQueryHandler
UserStatusHandler
+ PollHandler
DisconnectHandler
RawUpdateHandler
@@ -52,6 +52,9 @@ it.
.. autoclass:: UserStatusHandler()
:members:
+.. autoclass:: PollHandler()
+ :members:
+
.. autoclass:: DisconnectHandler()
:members:
diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst
index 609a38a8440..ded4d017bb3 100644
--- a/docs/source/api/methods.rst
+++ b/docs/source/api/methods.rst
@@ -3,13 +3,12 @@ Available Methods
All Pyrogram methods listed here are bound to a :obj:`Client ` instance.
-**Example:**
-
.. code-block:: python
+ :emphasize-lines: 6
from pyrogram import Client
- app = Client(...)
+ app = Client("my_account")
with app:
app.send_message("haskell", "hi")
diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst
index 710a772d75e..506fe003f83 100644
--- a/docs/source/api/types.rst
+++ b/docs/source/api/types.rst
@@ -3,9 +3,8 @@ Available Types
All Pyrogram types listed here are accessible through the main package directly.
-**Example:**
-
.. code-block:: python
+ :emphasize-lines: 1
from pyrogram import User, Message, ...
@@ -112,167 +111,72 @@ InputMessageContent
------------
.. autoclass:: User()
- :members:
-
.. autoclass:: UserStatus()
- :members:
-
.. autoclass:: Chat()
- :members:
-
.. autoclass:: ChatPreview()
- :members:
-
.. autoclass:: ChatPhoto()
- :members:
-
.. autoclass:: ChatMember()
- :members:
-
.. autoclass:: ChatMembers()
- :members:
-
.. autoclass:: ChatPermissions()
- :members:
-
.. autoclass:: Dialog()
- :members:
-
.. autoclass:: Dialogs()
- :members:
.. Messages & Media
----------------
.. autoclass:: Message()
- :members:
-
.. autoclass:: Messages()
- :members:
-
.. autoclass:: MessageEntity()
- :members:
-
.. autoclass:: Photo()
- :members:
-
.. autoclass:: PhotoSize()
- :members:
-
.. autoclass:: UserProfilePhotos()
- :members:
-
.. autoclass:: Audio()
- :members:
-
.. autoclass:: Document()
- :members:
-
.. autoclass:: Animation()
- :members:
-
.. autoclass:: Video()
- :members:
-
.. autoclass:: Voice()
- :members:
-
.. autoclass:: VideoNote()
- :members:
-
.. autoclass:: Contact()
- :members:
-
.. autoclass:: Location()
- :members:
-
.. autoclass:: Venue()
- :members:
-
.. autoclass:: Sticker()
- :members:
-
.. autoclass:: Game()
- :members:
-
.. autoclass:: Poll()
- :members:
-
.. autoclass:: PollOption()
- :members:
.. Keyboards
---------
.. autoclass:: ReplyKeyboardMarkup()
- :members:
-
.. autoclass:: KeyboardButton()
- :members:
-
.. autoclass:: ReplyKeyboardRemove()
- :members:
-
.. autoclass:: InlineKeyboardMarkup()
- :members:
-
.. autoclass:: InlineKeyboardButton()
- :members:
-
.. autoclass:: ForceReply()
- :members:
-
.. autoclass:: CallbackQuery()
- :members:
-
.. autoclass:: GameHighScore()
- :members:
-
.. autoclass:: CallbackGame()
- :members:
.. Input Media
-----------
.. autoclass:: InputMedia()
- :members:
-
.. autoclass:: InputMediaPhoto()
- :members:
-
.. autoclass:: InputMediaVideo()
- :members:
-
.. autoclass:: InputMediaAudio()
- :members:
-
.. autoclass:: InputMediaAnimation()
- :members:
-
.. autoclass:: InputMediaDocument()
- :members:
-
.. autoclass:: InputPhoneContact()
- :members:
-
.. Inline Mode
-----------
.. autoclass:: InlineQuery()
- :members:
-
.. autoclass:: InlineQueryResult()
- :members:
-
.. autoclass:: InlineQueryResultArticle()
- :members:
.. InputMessageContent
-------------------
.. autoclass:: InputMessageContent()
- :members:
-
.. autoclass:: InputTextMessageContent()
- :members:
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 9ff0f720754..9b878bafc29 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -66,7 +66,7 @@ How the Documentation is Organized
Contents are organized into self-contained topics and can be all accessed from the sidebar, or by following them in
order using the Next button at the end of each page. Here below you can, instead, find a list of the most relevant
-pages.
+pages for a quick access.
Getting Started
^^^^^^^^^^^^^^^
@@ -86,23 +86,27 @@ API Reference
- `Client Class`_ - Details about the Client class.
- `Available Methods`_ - A list of available high-level methods.
- `Available Types`_ - A list of available high-level types.
+- `Bound Methods`_ - A list of convenient bound methods.
.. _Client Class: api/client
.. _Available Methods: api/methods
.. _Available Types: api/types
+.. _Bound Methods: api/bound-methods
-Topics
-^^^^^^
+Relevant Topics
+^^^^^^^^^^^^^^^
- `Smart Plugins`_ - How to modularize your application.
- `Advanced Usage`_ - How to use Telegram's raw API.
- `Release Notes`_ - Release notes for Pyrogram releases.
- `Pyrogram FAQ`_ - Answers to common Pyrogram questions.
+- `Pyrogram Glossary`_ - A list of words with brief explanations.
.. _Smart Plugins: topics/smart-plugins
.. _Advanced Usage: topics/advanced-usage
.. _Release Notes: topics/releases
.. _Pyrogram FAQ: topics/faq
+.. _Pyrogram Glossary: topics/glossary
.. toctree::
:hidden:
@@ -128,6 +132,7 @@ Topics
api/client
api/methods
api/types
+ api/bound-methods
api/handlers
api/decorators
api/filters
@@ -152,6 +157,7 @@ Topics
topics/voice-calls
topics/releases
topics/faq
+ topics/glossary
.. toctree::
:hidden:
diff --git a/docs/source/intro/auth.rst b/docs/source/intro/auth.rst
index 86217137a4e..846a19a151d 100644
--- a/docs/source/intro/auth.rst
+++ b/docs/source/intro/auth.rst
@@ -61,6 +61,7 @@ after the session name, which will be ``pyrogrambot.session`` for the example be
"my_bot",
bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
)
+
app.run()
.. _project is set up: setup.html
diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst
index e2e4365a0f4..c4df60d45be 100644
--- a/docs/source/intro/install.rst
+++ b/docs/source/intro/install.rst
@@ -29,12 +29,12 @@ Install Pyrogram
Bleeding Edge
-------------
-Things are constantly evolving in Pyrogram, although new releases are published only when enough changes are added,
-but this doesn't mean you can't try new features right now!
+Pyrogram is always evolving, although new releases on PyPI are published only when enough changes are added, but this
+doesn't mean you can't try new features right now!
-In case you would like to try out the latest Pyrogram features and additions, the `GitHub repo`_ is always kept updated
-with new changes; you can install the development version straight from the ``develop`` branch using this command
-(note "develop.zip" in the link):
+In case you'd like to try out the latest Pyrogram features, the `GitHub repo`_ is always kept updated with new changes;
+you can install the development version straight from the ``develop`` branch using this command (note "develop.zip" in
+the link):
.. code-block:: text
@@ -44,7 +44,8 @@ Asynchronous
------------
Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging framework after all), and here's
-where asyncio shines the most by providing extra performance while running on a single OS-level thread only.
+where asyncio shines the most by providing extra performance and efficiency while running on a single OS-level thread
+only.
**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3+ required).
Use this command to install (note "asyncio.zip" in the link):
diff --git a/docs/source/intro/start.rst b/docs/source/intro/start.rst
index 7d6f0150b5e..2fcd46331ff 100644
--- a/docs/source/intro/start.rst
+++ b/docs/source/intro/start.rst
@@ -10,7 +10,7 @@ Get Pyrogram Real Fast
2. Get your own Telegram API key from https://my.telegram.org/apps.
-3. Open your best text editor and paste the following:
+3. Open your best text editor and paste the following:
.. code-block:: python
@@ -40,10 +40,9 @@ Enjoy the API
-------------
That was just a quick overview that barely scratched the surface!
-In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done.
+In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done above.
-Feeling eager? You can take a shortcut to `Calling Methods`_ and come back later to learn some more
-details.
+Feeling eager to continue? You can take a shortcut to `Calling Methods`_ and come back later to learn some more details.
.. _community: //t.me/pyrogramchat
.. _Calling Methods: ../start/invoking
\ No newline at end of file
diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst
index 1260672e06a..1f4f5a2e251 100644
--- a/docs/source/start/errors.rst
+++ b/docs/source/start/errors.rst
@@ -1,51 +1,57 @@
Error Handling
==============
-Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks.
+Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks in order
+to control the behaviour of your application. Pyrogram errors all live inside the ``errors`` package:
-There are many errors that Telegram could return, but they all fall in one of these categories
-(which are in turn children of the ``RPCError`` superclass):
+.. code-block:: python
+
+ from pyrogram import errors
+
+RPCError
+--------
+
+The father of all errors is named ``RPCError``. This error exists in form of a Python exception and is able to catch all
+Telegram API related errors.
+
+.. code-block:: python
+
+ from pyrogram.errors import RPCError
+
+.. warning::
+
+ It must be noted that catching this error is bad practice, especially when no feedback is given (i.e. by
+ logging/printing the full error traceback), because it makes it impossible to understand what went wrong.
-- `303 - See Other <../api/errors#see-other>`_
-- `400 - Bad Request <../api/errors#bad-request>`_
+Error Categories
+----------------
+
+The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram
+provides categories of errors, which are named after the common HTTP errors:
+
+.. code-block:: python
+
+ from pyrogram.errors import BadRequest, Forbidden, ...
+
+- `303 - SeeOther <../api/errors#seeother>`_
+- `400 - BadRequest <../api/errors#badrequest>`_
- `401 - Unauthorized <../api/errors#unauthorized>`_
- `403 - Forbidden <../api/errors#forbidden>`_
-- `406 - Not Acceptable <../api/errors#not-acceptable>`_
+- `406 - NotAcceptable <../api/errors#notacceptable>`_
- `420 - Flood <../api/errors#flood>`_
-- `500 - Internal Server Error <../api/errors#internal-server-error>`_
+- `500 - InternalServerError <../api/errors#internalservererror>`_
-As stated above, there are really many (too many) errors, and in case Pyrogram does not know anything yet about a
-specific one, it raises a special ``520 Unknown Error`` exception and logs it
-in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors.
+Unknown Errors
+--------------
-Examples
---------
-
-.. code-block:: python
+In case Pyrogram does not know anything yet about a specific error, it raises a special ``520 - UnknownError`` exception
+and logs it in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors.
- from pyrogram.errors import (
- BadRequest, Flood, InternalServerError,
- SeeOther, Unauthorized, UnknownError
- )
+Errors with Values
+------------------
- try:
- ...
- except BadRequest:
- pass
- except Flood:
- pass
- except InternalServerError:
- pass
- except SeeOther:
- pass
- except Unauthorized:
- pass
- except UnknownError:
- pass
-
-Exception objects may also contain some informative values.
-E.g.: ``FloodWait`` holds the amount of seconds you have to wait
-before you can try again. The value is always stored in the ``x`` field of the returned exception object:
+Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you
+have to wait before you can try again. The value is always stored in the ``x`` field of the returned exception object:
.. code-block:: python
@@ -55,4 +61,4 @@ before you can try again. The value is always stored in the ``x`` field of the r
try:
...
except FloodWait as e:
- time.sleep(e.x)
+ time.sleep(e.x) # Wait before trying again
diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst
index c27e86c36fb..fae1952340a 100644
--- a/docs/source/start/invoking.rst
+++ b/docs/source/start/invoking.rst
@@ -1,9 +1,12 @@
Calling Methods
===============
-At this point, we have successfully `installed Pyrogram`_ and authorized_ our account; we are now pointing towards the
+At this point, we have successfully `installed Pyrogram`_ and authorized_ our account; we are now aiming towards the
core of the library. It's time to start playing with the API!
+Basic Usage
+-----------
+
Making API method calls with Pyrogram is very simple. Here's an example we are going to examine:
.. code-block:: python
@@ -60,8 +63,8 @@ Context Manager
---------------
You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically
-:meth:`start ` and :meth:`stop ` gracefully, even in case of unhandled
-exceptions in your code. The example above can be therefore rewritten in a much nicer way, this way:
+:meth:`start() ` and :meth:`stop() ` gracefully, even in case of unhandled
+exceptions in your code. The example above can be therefore rewritten in a much nicer way:
.. code-block:: python
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index 85005b9c712..930096a3b2a 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -8,7 +8,7 @@ Defining Updates
----------------
First, let's define what are these updates. As hinted already, updates are simply events that happen in your Telegram
-account (incoming messages, new members join, button presses, etc...), which are meant to notify you about a new
+account (incoming messages, new members join, bot button presses, etc...), which are meant to notify you about a new
specific state that changed. These updates are handled by registering one or more callback functions in your app using
`Handlers <../api/handlers>`_.
@@ -70,7 +70,7 @@ begins.
app.add_handler(my_handler)
-Last one, the :meth:`run() ` method. What this does is simply calling
+Last one, the :meth:`run() ` method. What this does is simply call
:meth:`start() ` and a special method :meth:`idle() ` that keeps your main
scripts alive until you press ``CTRL+C``; the client will be automatically stopped after that.
diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst
index 970e07e0496..02a3e3b62ec 100644
--- a/docs/source/topics/advanced-usage.rst
+++ b/docs/source/topics/advanced-usage.rst
@@ -101,9 +101,9 @@ sending messages with IDs only thanks to cached access hashes.
There are three different InputPeer types, one for each kind of Telegram entity.
Whenever an InputPeer is needed you must pass one of these:
- - `InputPeerUser `_ - Users
- - `InputPeerChat `_ - Basic Chats
- - `InputPeerChannel `_ - Either Channels or Supergroups
+ - :obj:`InputPeerUser <../telegram/types/InputPeerUser>` - Users
+ - :obj:`InputPeerChat <../telegram/types/InputPeerChat>` - Basic Chats
+ - :obj:`InputPeerChannel <../telegram/types/InputPeerChannel>` - Either Channels or Supergroups
But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides
:meth:`resolve_peer() ` as a convenience utility method that returns the correct InputPeer
diff --git a/docs/source/topics/faq.rst b/docs/source/topics/faq.rst
index 0834389ea2a..7f2086a165e 100644
--- a/docs/source/topics/faq.rst
+++ b/docs/source/topics/faq.rst
@@ -17,21 +17,51 @@ C. It enables you to easily create custom applications for both user and bot ide
.. _Telegram: https://telegram.org
.. _MTProto API: https://core.telegram.org/api#telegram-api
-What does "Pyrogram" mean?
---------------------------
+What does the name mean?
+------------------------
The word "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**,
from *Telegram*. The word *pyro* itself is built from *Python*, **py** for short, and the suffix **ro** to come up with
the word *fire*, which also inspired the project logo.
-How old is Pyrogram?
---------------------
+How old is the project?
+-----------------------
Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior the
initial public release on `GitHub`_.
.. _GitHub: https://github.com/pyrogram/pyrogram
+Why Pyrogram?
+-------------
+
+- **Easy**: You can install Pyrogram with pip and start building your applications right away.
+- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way.
+- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
+- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
+- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
+- **Updated**, to make use of the latest Telegram API version and features.
+- **Pluggable**: The `Smart Plugin`_ system allows to write components with minimal boilerplate code.
+- **Comprehensive**: Execute any `advanced action`_ an official client is able to do, and even more.
+
+.. _TgCrypto: https://github.com/pyrogram/tgcrypto
+.. _Smart Plugin: smart-plugins
+.. _advanced action: advanced-usage
+
+What can MTProto do more than the Bot API?
+------------------------------------------
+
+- Authorize both user and bot identities.
+- Upload & download any file, up to 1500 MB each.
+- Has less overhead due to direct connections to the actual Telegram servers.
+- Run multiple sessions at once, up to 10 per account (either bot or user).
+- Get information about any public chat by usernames, even if not a member.
+- Obtain information about any message existing in a chat using message ids.
+- retrieve the whole chat members list of either public or private chats.
+- Receive extra updates, such as the one about a user name change.
+- More meaningful errors in case something went wrong.
+- Get API version updates, and thus new features, sooner.
+
Why do I need an API key for bots?
----------------------------------
@@ -46,6 +76,8 @@ Using MTProto is the only way to communicate with the actual Telegram servers, a
identify applications by means of a unique key; the bot token identifies a bot as a user and replaces the user's phone
number only.
+Why is the main API (MTProto) superiod
+
I started a client but nothing happens!
---------------------------------------
@@ -65,7 +97,7 @@ in a bunch of seconds:
.. _you need a proxy: proxy
-I keep getting [400 PEER_ID_INVALID] error!
+I keep getting PEER_ID_INVALID error!
-------------------------------------------
The error in question is **[400 PEER_ID_INVALID]: The id/access_hash combination is invalid**, and could mean several
@@ -104,4 +136,18 @@ mistakes by either the automatic systems or a moderator. In such cases you can k
recover@telegram.org, contact `@smstelegram`_ on Twitter or use `this form`_.
.. _@smstelegram: https://twitter.com/smstelegram
-.. _this form: https://telegram.org/support
\ No newline at end of file
+.. _this form: https://telegram.org/support
+
+About the License
+-----------------
+
+.. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png
+ :align: left
+
+Pyrogram is free software and is currently licensed under the terms of the GNU Lesser General Public License v3 or later
+(LGPLv3+). In short: you may use, redistribute and/or modify it provided that modifications are described and licensed
+for free under LGPLv3+.
+
+In other words: you can use and integrate Pyrogram into your own code --- either open source, under the same or a
+different licence or even proprietary --- without being required to release the source code of your own applications.
+However, any modifications to the library itself are required to be published for free under the same LGPLv3+ license.
\ No newline at end of file
diff --git a/docs/source/topics/filters.rst b/docs/source/topics/filters.rst
index ec3e2e10c02..cb2e2a4c182 100644
--- a/docs/source/topics/filters.rst
+++ b/docs/source/topics/filters.rst
@@ -7,6 +7,9 @@ but there's much more than that to come.
Here we'll discuss about :class:`Filters `. Filters enable a fine-grain control over what kind of
updates are allowed or not to be passed in your callback functions, based on their inner details.
+Single Filters
+--------------
+
Let's start right away with a simple example:
- This example will show you how to **only** handle messages containing an :obj:`Audio ` object and
diff --git a/docs/source/topics/glossary.rst b/docs/source/topics/glossary.rst
new file mode 100644
index 00000000000..5721306c6b5
--- /dev/null
+++ b/docs/source/topics/glossary.rst
@@ -0,0 +1,53 @@
+Pyrogram Glossary
+-----------------
+
+This page contains a list of common words with brief explanations related to Pyrogram and, to some extent, Telegram in
+general.
+
+.. glossary::
+
+ API
+ Application Programming Interface: a set of methods, protocols and tools that make it easier to develop programs
+ by providing useful building blocks to the developer.
+
+ API key
+ A secret code used to authenticate and/or authorize a specific application to Telegram in order for it to
+ control how the API is being used, for example, to prevent abuses of the API.
+
+ MTProto
+ The name of the custom-made, open encryption protocol by Telegram, implemented in Pyrogram.
+
+ MTProto API
+ The Telegram main API Pyrogram makes use of, which is able to connect both users and normal bots to Telegram
+ using MTProto as application layer protocol and execute any method Telegram provides from its public schema.
+
+ Bot API
+ The `Telegram Bot API`_ that is able to only connect normal bots to Telegram using HTTP as application layer
+ protocol and allows to execute a subset of the main Telegram API.
+
+ Pyrogrammer
+ A developer that uses Pyrogram to build Telegram applications.
+
+ Userbot
+ Also known as *user bot* or *ubot* for short, is a user logged in by third-party Telegram libraries --- such as
+ Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event.
+
+ Session
+ Also known as *login session*, is a strictly personal piece of information created and held by both parties
+ (client and server) which is used to grant permission into a single account without having to start a new
+ authorization process from scratch.
+
+ Callback
+ Also known as *callback function*, is a user-defined generic function that *can be* registered to and then
+ called-back by the framework when specific events occurs.
+
+ Handler
+ An object that wraps around a callback function that is *actually meant* to be registered into the framework,
+ which will then be able to handle a specific kind of events, such as a new incoming message, for example.
+
+ Decorator
+ Also known as *function decorator*, in Python, is a callable object that is used to modify another function.
+ Decorators in Pyrogram are used to automatically register callback functions for `handling updates`_.
+
+.. _Telegram Bot API: https://core.telegram.org/bots/api
+.. _handling updates: ../start/updates
\ No newline at end of file
diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst
index fbe2738893f..9f1592d1bd9 100644
--- a/docs/source/topics/smart-plugins.rst
+++ b/docs/source/topics/smart-plugins.rst
@@ -30,7 +30,7 @@ after importing your modules, like this:
handlers.py
main.py
-- ``handlers.py``
+- ``handlers.py``
.. code-block:: python
@@ -41,7 +41,7 @@ after importing your modules, like this:
def echo_reversed(client, message):
message.reply(message.text[::-1])
-- ``main.py``
+- ``main.py``
.. code-block:: python
@@ -91,7 +91,7 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
config.ini
main.py
-- ``plugins/handlers.py``
+- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 4, 9
@@ -108,14 +108,14 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight
def echo_reversed(client, message):
message.reply(message.text[::-1])
-- ``config.ini``
+- ``config.ini``
.. code-block:: ini
[plugins]
root = plugins
-- ``main.py``
+- ``main.py``
.. code-block:: python
@@ -199,8 +199,8 @@ also organized in subfolders:
...
...
-- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order
- (files) and definition order (handlers inside files):
+- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order
+ (files) and definition order (handlers inside files):
Using *config.ini* file:
@@ -217,7 +217,7 @@ also organized in subfolders:
Client("my_account", plugins=plugins).run()
-- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order:
+- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order:
Using *config.ini* file:
@@ -243,7 +243,7 @@ also organized in subfolders:
Client("my_account", plugins=plugins).run()
-- Load everything except the handlers inside *plugins2.py*:
+- Load everything except the handlers inside *plugins2.py*:
Using *config.ini* file:
@@ -264,7 +264,7 @@ also organized in subfolders:
Client("my_account", plugins=plugins).run()
-- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*:
+- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*:
Using *config.ini* file:
@@ -297,7 +297,7 @@ Each function decorated with the usual ``on_message`` decorator (or any other de
*(handler: Handler, group: int)*. The actual callback function is therefore stored inside the handler's *callback*
attribute. Here's an example:
-- ``plugins/handlers.py``
+- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 5, 6
@@ -321,7 +321,7 @@ In order to unload a plugin, or any other handler, all you need to do is obtain
relevant module and call :meth:`remove_handler() ` Client's method with your function
name preceded by the star ``*`` operator as argument. Example:
-- ``main.py``
+- ``main.py``
.. code-block:: python
@@ -345,7 +345,7 @@ Loading
Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time
using :meth:`add_handler() ` instead. Example:
-- ``main.py``
+- ``main.py``
.. code-block:: python
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 31a7c14cf3c..d382b2fb71d 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -60,9 +60,7 @@
class Client(Methods, BaseClient):
- """This class represents a Client, the main means for interacting with Telegram.
- It exposes bot-like methods for an easy access to the API as well as a simple way to
- invoke every single Telegram API method available.
+ """Pyrogram Client, the main means for interacting with Telegram.
Parameters:
session_name (``str``):
@@ -80,7 +78,7 @@ class Client(Methods, BaseClient):
This is an alternative way to pass it if you don't want to use the *config.ini* file.
app_version (``str``, *optional*):
- Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z"
+ Application version. Defaults to "Pyrogram :fire: vX.Y.Z"
This is an alternative way to set it if you don't want to use the *config.ini* file.
device_model (``str``, *optional*):
@@ -110,6 +108,10 @@ class Client(Methods, BaseClient):
Only applicable for new sessions and will be ignored in case previously
created sessions are loaded.
+ bot_token (``str``, *optional*):
+ Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+ Only applicable for new sessions.
+
phone_number (``str`` | ``callable``, *optional*):
Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually.
Or pass a callback function which accepts no arguments and must return the correct phone number as string
@@ -143,10 +145,6 @@ class Client(Methods, BaseClient):
a new Telegram account in case the phone number you passed is not registered yet.
Only applicable for new sessions.
- bot_token (``str``, *optional*):
- Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
- Only applicable for new sessions.
-
last_name (``str``, *optional*):
Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can
be an empty string: "". Only applicable for new sessions.
@@ -193,12 +191,12 @@ def __init__(
ipv6: bool = False,
proxy: dict = None,
test_mode: bool = False,
+ bot_token: str = None,
phone_number: str = None,
phone_code: Union[str, callable] = None,
password: str = None,
recovery_code: callable = None,
force_sms: bool = False,
- bot_token: str = None,
first_name: str = None,
last_name: str = None,
workers: int = BaseClient.WORKERS,
@@ -221,12 +219,12 @@ def __init__(
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
self.test_mode = test_mode
+ self.bot_token = bot_token
self.phone_number = phone_number
self.phone_code = phone_code
self.password = password
self.recovery_code = recovery_code
self.force_sms = force_sms
- self.bot_token = bot_token
self.first_name = first_name
self.last_name = last_name
self.workers = workers
diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py
index 2117ec6e219..169193a0e88 100644
--- a/pyrogram/client/filters/filters.py
+++ b/pyrogram/client/filters/filters.py
@@ -191,35 +191,19 @@ class Filters:
"""Filter messages sent via inline bots"""
service = create("Service", lambda _, m: bool(m.service))
- """Filter service messages. A service message contains any of the following fields set
+ """Filter service messages.
- - left_chat_member
- - new_chat_title
- - new_chat_photo
- - delete_chat_photo
- - group_chat_created
- - supergroup_chat_created
- - channel_chat_created
- - migrate_to_chat_id
- - migrate_from_chat_id
- - pinned_message
- - game_score"""
+ A service message contains any of the following fields set: *left_chat_member*,
+ *new_chat_title*, *new_chat_photo*, *delete_chat_photo*, *group_chat_created*, *supergroup_chat_created*,
+ *channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*.
+ """
media = create("Media", lambda _, m: bool(m.media))
- """Filter media messages. A media message contains any of the following fields set
+ """Filter media messages.
- - audio
- - document
- - photo
- - sticker
- - video
- - animation
- - voice
- - video_note
- - contact
- - location
- - venue
- - poll"""
+ A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*,
+ *animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*.
+ """
@staticmethod
def command(
diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py
index 9ead25b408e..b76655fbfb7 100644
--- a/pyrogram/client/methods/decorators/on_callback_query.py
+++ b/pyrogram/client/methods/decorators/on_callback_query.py
@@ -30,7 +30,8 @@ def on_callback_query(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling callback queries.
+ """Decorator for handling callback queries.
+
This does the same thing as :meth:`add_handler` using the :class:`CallbackQueryHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py
index eb9dfcd0d46..7637e6eb60c 100644
--- a/pyrogram/client/methods/decorators/on_deleted_messages.py
+++ b/pyrogram/client/methods/decorators/on_deleted_messages.py
@@ -30,7 +30,8 @@ def on_deleted_messages(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling deleted messages.
+ """Decorator for handling deleted messages.
+
This does the same thing as :meth:`add_handler` using the :class:`DeletedMessagesHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py
index 515a28c163f..9305808eebd 100644
--- a/pyrogram/client/methods/decorators/on_disconnect.py
+++ b/pyrogram/client/methods/decorators/on_disconnect.py
@@ -23,7 +23,8 @@
class OnDisconnect(BaseClient):
def on_disconnect(self=None) -> callable:
- """Use this decorator to automatically register a function for handling disconnections.
+ """Decorator for handling disconnections.
+
This does the same thing as :meth:`add_handler` using the :class:`DisconnectHandler`.
"""
diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/client/methods/decorators/on_inline_query.py
index e9758c64c10..588373980b2 100644
--- a/pyrogram/client/methods/decorators/on_inline_query.py
+++ b/pyrogram/client/methods/decorators/on_inline_query.py
@@ -30,7 +30,8 @@ def on_inline_query(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling inline queries.
+ """Decorator for handling inline queries.
+
This does the same thing as :meth:`add_handler` using the :class:`InlineQueryHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py
index ad95cd451e1..f590fd12ddb 100644
--- a/pyrogram/client/methods/decorators/on_message.py
+++ b/pyrogram/client/methods/decorators/on_message.py
@@ -30,7 +30,8 @@ def on_message(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling messages.
+ """Decorator for handling messages.
+
This does the same thing as :meth:`add_handler` using the :class:`MessageHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py
index 68f3d78e687..de1c1d3df34 100644
--- a/pyrogram/client/methods/decorators/on_poll.py
+++ b/pyrogram/client/methods/decorators/on_poll.py
@@ -30,7 +30,8 @@ def on_poll(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling poll updates.
+ """Decorator for handling poll updates.
+
This does the same thing as :meth:`add_handler` using the :class:`PollHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py
index 2ab1f61bd07..53a0f4cf6a4 100644
--- a/pyrogram/client/methods/decorators/on_raw_update.py
+++ b/pyrogram/client/methods/decorators/on_raw_update.py
@@ -28,7 +28,8 @@ def on_raw_update(
self=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling raw updates.
+ """Decorator for handling raw updates.
+
This does the same thing as :meth:`add_handler` using the :class:`RawUpdateHandler`.
Parameters:
diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py
index 5b49bb3cd72..e7db7b7499a 100644
--- a/pyrogram/client/methods/decorators/on_user_status.py
+++ b/pyrogram/client/methods/decorators/on_user_status.py
@@ -30,7 +30,7 @@ def on_user_status(
filters=None,
group: int = 0
) -> callable:
- """Use this decorator to automatically register a function for handling user status updates.
+ """Decorator for handling user status updates.
This does the same thing as :meth:`add_handler` using the :class:`UserStatusHandler`.
Parameters:
diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py
index a3e161fb3c2..82c35f92624 100644
--- a/pyrogram/client/methods/messages/forward_messages.py
+++ b/pyrogram/client/methods/messages/forward_messages.py
@@ -28,7 +28,7 @@ def forward_messages(
self,
chat_id: Union[int, str],
from_chat_id: Union[int, str],
- message_ids: Iterable[int],
+ message_ids: Union[int, Iterable[int]],
disable_notification: bool = None,
as_copy: bool = False,
remove_caption: bool = False
diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py
index e966a11ae8d..66b3f1c90dc 100644
--- a/pyrogram/client/methods/messages/send_document.py
+++ b/pyrogram/client/methods/messages/send_document.py
@@ -46,7 +46,7 @@ def send_document(
progress: callable = None,
progress_args: tuple = ()
) -> Union["pyrogram.Message", None]:
- """Send general files.
+ """Send generic files.
Parameters:
chat_id (``int`` | ``str``):
diff --git a/pyrogram/client/types/keyboards/callback_query.py b/pyrogram/client/types/keyboards/callback_query.py
index 4d657767a76..fe6a2175f4b 100644
--- a/pyrogram/client/types/keyboards/callback_query.py
+++ b/pyrogram/client/types/keyboards/callback_query.py
@@ -30,9 +30,9 @@
class CallbackQuery(PyrogramType, Update):
"""An incoming callback query from a callback button in an inline keyboard.
- If the button that originated the query was attached to a message sent by the bot, the field message
- will be present. If the button was attached to a message sent via the bot (in inline mode),
- the field inline_message_id will be present. Exactly one of the fields data or game_short_name will be present.
+ If the button that originated the query was attached to a message sent by the bot, the field *message*
+ will be present. If the button was attached to a message sent via the bot (in inline mode), the field
+ *inline_message_id* will be present. Exactly one of the fields *data* or *game_short_name* will be present.
Parameters:
id (``str``):
diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py
index a8f6d31be26..e51f342505f 100644
--- a/pyrogram/client/types/messages_and_media/message.py
+++ b/pyrogram/client/types/messages_and_media/message.py
@@ -661,7 +661,7 @@ def reply(
reply_to_message_id: int = None,
reply_markup=None
) -> "Message":
- """Bound method *reply* of :obj:`Message`.
+ """Bound method *reply* :obj:`Message `.
Use as a shortcut for:
@@ -748,7 +748,7 @@ def reply_animation(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_animation* of :obj:`Message`.
+ """Bound method *reply_animation* :obj:`Message `.
Use as a shortcut for:
@@ -882,7 +882,7 @@ def reply_audio(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_audio* of :obj:`Message`.
+ """Bound method *reply_audio* :obj:`Message `.
Use as a shortcut for:
@@ -1010,7 +1010,7 @@ def reply_cached_media(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_cached_media* of :obj:`Message`.
+ """Bound method *reply_cached_media* :obj:`Message `.
Use as a shortcut for:
@@ -1077,7 +1077,7 @@ def reply_cached_media(
)
def reply_chat_action(self, action: str) -> bool:
- """Bound method *reply_chat_action* of :obj:`Message`.
+ """Bound method *reply_chat_action* :obj:`Message `.
Use as a shortcut for:
@@ -1130,7 +1130,7 @@ def reply_contact(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_contact* of :obj:`Message`.
+ """Bound method *reply_contact* :obj:`Message `.
Use as a shortcut for:
@@ -1217,7 +1217,7 @@ def reply_document(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_document* of :obj:`Message`.
+ """Bound method *reply_document* :obj:`Message `.
Use as a shortcut for:
@@ -1331,7 +1331,7 @@ def reply_game(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_game* of :obj:`Message`.
+ """Bound method *reply_game* :obj:`Message `.
Use as a shortcut for:
@@ -1396,7 +1396,7 @@ def reply_inline_bot_result(
reply_to_message_id: int = None,
hide_via: bool = None
) -> "Message":
- """Bound method *reply_inline_bot_result* of :obj:`Message`.
+ """Bound method *reply_inline_bot_result* :obj:`Message `.
Use as a shortcut for:
@@ -1470,7 +1470,7 @@ def reply_location(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_location* of :obj:`Message`.
+ """Bound method *reply_location* :obj:`Message `.
Use as a shortcut for:
@@ -1538,7 +1538,7 @@ def reply_media_group(
disable_notification: bool = None,
reply_to_message_id: int = None
) -> "Message":
- """Bound method *reply_media_group* of :obj:`Message`.
+ """Bound method *reply_media_group* :obj:`Message `.
Use as a shortcut for:
@@ -1610,7 +1610,7 @@ def reply_photo(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_photo* of :obj:`Message`.
+ """Bound method *reply_photo* :obj:`Message `.
Use as a shortcut for:
@@ -1724,7 +1724,7 @@ def reply_poll(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_poll* of :obj:`Message`.
+ """Bound method *reply_poll* :obj:`Message `.
Use as a shortcut for:
@@ -1800,7 +1800,7 @@ def reply_sticker(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_sticker* of :obj:`Message`.
+ """Bound method *reply_sticker* :obj:`Message `.
Use as a shortcut for:
@@ -1903,7 +1903,7 @@ def reply_venue(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *reply_venue* of :obj:`Message`.
+ """Bound method *reply_venue* :obj:`Message `.
Use as a shortcut for:
@@ -2005,7 +2005,7 @@ def reply_video(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_video* of :obj:`Message`.
+ """Bound method *reply_video* :obj:`Message `.
Use as a shortcut for:
@@ -2140,7 +2140,7 @@ def reply_video_note(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_video_note* of :obj:`Message`.
+ """Bound method *reply_video_note* :obj:`Message `.
Use as a shortcut for:
@@ -2258,7 +2258,7 @@ def reply_voice(
progress: callable = None,
progress_args: tuple = ()
) -> "Message":
- """Bound method *reply_voice* of :obj:`Message`.
+ """Bound method *reply_voice* :obj:`Message `.
Use as a shortcut for:
@@ -2368,7 +2368,7 @@ def edit(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *edit* of :obj:`Message`
+ """Bound method *edit* :obj:`Message `.
Use as a shortcut for:
@@ -2425,7 +2425,7 @@ def edit_caption(
"pyrogram.ForceReply"
] = None
) -> "Message":
- """Bound method *edit_caption* of :obj:`Message`
+ """Bound method *edit_caption* :obj:`Message `.
Use as a shortcut for:
@@ -2468,7 +2468,7 @@ def edit_caption(
)
def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message":
- """Bound method *edit_media* of :obj:`Message`
+ """Bound method *edit_media* :obj:`Message `.
Use as a shortcut for:
@@ -2506,7 +2506,7 @@ def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMa
)
def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message":
- """Bound method *edit_reply_markup* of :obj:`Message`
+ """Bound method *edit_reply_markup* :obj:`Message `.
Use as a shortcut for:
@@ -2547,7 +2547,7 @@ def forward(
as_copy: bool = False,
remove_caption: bool = False
) -> "Message":
- """Bound method *forward* of :obj:`Message`.
+ """Bound method *forward* :obj:`Message `.
Use as a shortcut for:
@@ -2690,7 +2690,7 @@ def forward(
)
def delete(self, revoke: bool = True):
- """Bound method *delete* of :obj:`Message`.
+ """Bound method *delete* :obj:`Message `.
Use as a shortcut for:
@@ -2726,7 +2726,7 @@ def delete(self, revoke: bool = True):
)
def click(self, x: int or str, y: int = 0, quote: bool = None, timeout: int = 10):
- """Bound method *click* of :obj:`Message`.
+ """Bound method *click* :obj:`Message `.
Use as a shortcut for clicking a button attached to the message instead of:
@@ -2764,6 +2764,7 @@ def click(self, x: int or str, y: int = 0, quote: bool = None, timeout: int = 10
Parameters:
x (``int`` | ``str``):
Used as integer index, integer abscissa (in pair with y) or as string label.
+ Defaults to 0 (first button).
y (``int``, *optional*):
Used as ordinate only (in pair with x).
@@ -2853,7 +2854,7 @@ def download(
progress: callable = None,
progress_args: tuple = ()
) -> str:
- """Bound method *download* of :obj:`Message`.
+ """Bound method *download* :obj:`Message `.
Use as a shortcut for:
@@ -2902,7 +2903,7 @@ def download(
)
def pin(self, disable_notification: bool = None) -> "Message":
- """Bound method *pin* of :obj:`Message`.
+ """Bound method *pin* :obj:`Message `.
Use as a shortcut for:
diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py
index 37fde9fd3ea..08e43138678 100644
--- a/pyrogram/client/types/user_and_chats/chat_photo.py
+++ b/pyrogram/client/types/user_and_chats/chat_photo.py
@@ -25,7 +25,7 @@
class ChatPhoto(PyrogramType):
- """a chat photo.
+ """A chat photo.
Parameters:
small_file_id (``str``):
From fd69f45e985e2914fa6a174e471a20beacd331f8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 16 May 2019 21:29:09 +0200
Subject: [PATCH 0143/1652] Add CHAT_SEND_MEDIA_FORBIDDEN and
INVITE_HASH_EXPIRED error
---
compiler/error/source/400_BAD_REQUEST.tsv | 3 ++-
compiler/error/source/403_FORBIDDEN.tsv | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv
index 8325040d092..db2f0e58f54 100644
--- a/compiler/error/source/400_BAD_REQUEST.tsv
+++ b/compiler/error/source/400_BAD_REQUEST.tsv
@@ -96,4 +96,5 @@ EXTERNAL_URL_INVALID The external media URL is invalid
CHAT_NOT_MODIFIED The chat settings were not modified
RESULTS_TOO_MUCH The result contains too many items
RESULT_ID_DUPLICATE The result contains items with duplicated identifiers
-ACCESS_TOKEN_INVALID The bot access token is invalid
\ No newline at end of file
+ACCESS_TOKEN_INVALID The bot access token is invalid
+INVITE_HASH_EXPIRED The chat invite link is no longer valid
\ No newline at end of file
diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/error/source/403_FORBIDDEN.tsv
index 34433da7931..ddd8d26f544 100644
--- a/compiler/error/source/403_FORBIDDEN.tsv
+++ b/compiler/error/source/403_FORBIDDEN.tsv
@@ -2,4 +2,5 @@ id message
CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
-MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat
\ No newline at end of file
+MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat
+CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat
\ No newline at end of file
From 82c3bb2dba983cf9f7d7c1dedd1616380d63692b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 17 May 2019 13:13:58 +0200
Subject: [PATCH 0144/1652] Add three more internal server errors
---
compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
index d1c666c68c2..4dfe5994f72 100644
--- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
+++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
@@ -5,4 +5,7 @@ RPC_MCGET_FAIL Telegram is having internal problems. Please try again later
PERSISTENT_TIMESTAMP_OUTDATED Telegram is having internal problems. Please try again later
HISTORY_GET_FAILED Telegram is having internal problems. Please try again later
REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later
-RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later
\ No newline at end of file
+RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later
+WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later
+INTERDC_X_CALL_ERROR Telegram is having internal problems. Please try again later
+INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems. Please try again later
\ No newline at end of file
From 53d0cc30f644623ceff7977e05613b9d01cc818f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 17 May 2019 13:18:24 +0200
Subject: [PATCH 0145/1652] Remove get_chat_preview and have get_chat deal with
ChatPreview objects
---
pyrogram/client/methods/chats/__init__.py | 2 -
pyrogram/client/methods/chats/get_chat.py | 14 ++--
.../client/methods/chats/get_chat_preview.py | 67 -------------------
.../types/user_and_chats/chat_preview.py | 4 +-
4 files changed, 9 insertions(+), 78 deletions(-)
delete mode 100644 pyrogram/client/methods/chats/get_chat_preview.py
diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py
index 8db44abec60..698255f5e20 100644
--- a/pyrogram/client/methods/chats/__init__.py
+++ b/pyrogram/client/methods/chats/__init__.py
@@ -22,7 +22,6 @@
from .get_chat_member import GetChatMember
from .get_chat_members import GetChatMembers
from .get_chat_members_count import GetChatMembersCount
-from .get_chat_preview import GetChatPreview
from .get_dialogs import GetDialogs
from .iter_chat_members import IterChatMembers
from .iter_dialogs import IterDialogs
@@ -61,7 +60,6 @@ class Chats(
UnpinChatMessage,
GetDialogs,
GetChatMembersCount,
- GetChatPreview,
IterDialogs,
IterChatMembers,
UpdateChatUsername,
diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py
index 96bc9eaf156..4f71c3b3252 100644
--- a/pyrogram/client/methods/chats/get_chat.py
+++ b/pyrogram/client/methods/chats/get_chat.py
@@ -27,8 +27,9 @@ class GetChat(BaseClient):
def get_chat(
self,
chat_id: Union[int, str]
- ) -> "pyrogram.Chat":
- """Get up to date information about the chat.
+ ) -> Union["pyrogram.Chat", "pyrogram.ChatPreview"]:
+ """Get up to date information about a chat.
+
Information include current name of the user for one-on-one conversations, current username of a user, group or
channel, etc.
@@ -39,7 +40,8 @@ def get_chat(
of the target channel/supergroup (in the format @username).
Returns:
- :obj:`Chat`: On success, a chat object is returned.
+ :obj:`Chat` | :obj:`ChatPreview`: On success, if you've already joined the chat, a chat object is returned,
+ otherwise, a chat preview object is returned.
Raises:
RPCError: In case of a Telegram RPC error.
@@ -48,16 +50,14 @@ def get_chat(
match = self.INVITE_LINK_RE.match(str(chat_id))
if match:
- h = match.group(1)
-
r = self.send(
functions.messages.CheckChatInvite(
- hash=h
+ hash=match.group(1)
)
)
if isinstance(r, types.ChatInvite):
- raise ValueError("You haven't joined \"t.me/joinchat/{}\" yet".format(h))
+ return pyrogram.ChatPreview._parse(self, r)
self.fetch_peers([r.chat])
diff --git a/pyrogram/client/methods/chats/get_chat_preview.py b/pyrogram/client/methods/chats/get_chat_preview.py
deleted file mode 100644
index 8551aaf434c..00000000000
--- a/pyrogram/client/methods/chats/get_chat_preview.py
+++ /dev/null
@@ -1,67 +0,0 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-2019 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 .
-
-import pyrogram
-from pyrogram.api import functions, types
-from ...ext import BaseClient
-
-
-class GetChatPreview(BaseClient):
- def get_chat_preview(
- self,
- invite_link: str
- ):
- """Get the preview of a chat using the invite link.
-
- This method only returns a chat preview, if you want to join a chat use :meth:`join_chat`
-
- Parameters:
- invite_link (``str``):
- Unique identifier for the target chat in form of *t.me/joinchat/* links.
-
- Returns:
- :obj:`Chat`: In case you already joined the chat.
-
- :obj:`ChatPreview` -- In case you haven't joined the chat yet.
-
- Raises:
- RPCError: In case of a Telegram RPC error.
- ValueError: In case of an invalid invite link.
- """
- match = self.INVITE_LINK_RE.match(invite_link)
-
- if match:
- r = self.send(
- functions.messages.CheckChatInvite(
- hash=match.group(1)
- )
- )
-
- if isinstance(r, types.ChatInvite):
- return pyrogram.ChatPreview._parse(self, r)
-
- if isinstance(r, types.ChatInviteAlready):
- chat = r.chat
-
- if isinstance(chat, types.Chat):
- return pyrogram.Chat._parse_chat_chat(self, chat)
-
- if isinstance(chat, types.Channel):
- return pyrogram.Chat._parse_channel_chat(self, chat)
- else:
- raise ValueError("The invite_link is invalid")
diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py
index 0366d04ff40..38dda6b6b8e 100644
--- a/pyrogram/client/types/user_and_chats/chat_preview.py
+++ b/pyrogram/client/types/user_and_chats/chat_preview.py
@@ -32,7 +32,7 @@ class ChatPreview(PyrogramType):
title (``str``):
Title of the chat.
- photo (:obj:`ChatPhoto`):
+ photo (:obj:`ChatPhoto`, *optional*):
Chat photo. Suitable for downloads only.
type (``str``):
@@ -52,7 +52,7 @@ def __init__(
*,
client: "pyrogram.BaseClient" = None,
title: str,
- photo: ChatPhoto,
+ photo: ChatPhoto = None,
type: str,
members_count: int,
members: List[User] = None
From ddef2032e299173c7545ac5c1fc59dc8a9499a51 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 17 May 2019 13:21:51 +0200
Subject: [PATCH 0146/1652] Update README.md
---
README.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 35c80f37c44..4d202b4f0f8 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
Releases
•
-
+
Community
@@ -58,8 +58,7 @@ ground up in Python and C. It enables you to easily create custom apps for both
written in pure C.
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
-- **Updated**, to the latest Telegram API version, currently Layer 97 on top of
- [MTProto 2.0](https://core.telegram.org/mtproto).
+- **Updated**, to make use of the latest Telegram API version and features.
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
From 23d0ef3cf94b78041141e6efb0129f23082dd4fd Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 17 May 2019 13:23:03 +0200
Subject: [PATCH 0147/1652] Use "bot" as chat type for bots. We now have
"private", "bot", "group", "supergroups" and "channel" chat types.
---
pyrogram/client/types/user_and_chats/chat.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py
index 8793942c5f9..e45814ea8d5 100644
--- a/pyrogram/client/types/user_and_chats/chat.py
+++ b/pyrogram/client/types/user_and_chats/chat.py
@@ -33,33 +33,33 @@ class Chat(PyrogramType):
Unique identifier for this chat.
type (``str``):
- Type of chat, can be either "private", "group", "supergroup" or "channel".
+ Type of chat, can be either "private", "bot", "group", "supergroup" or "channel".
title (``str``, *optional*):
Title, for supergroups, channels and basic group chats.
username (``str``, *optional*):
- Username, for private chats, supergroups and channels if available.
+ Username, for private chats, bots, supergroups and channels if available.
first_name (``str``, *optional*):
- First name of the other party in a private chat.
+ First name of the other party in a private chat, for private chats and bots.
last_name (``str``, *optional*):
- Last name of the other party in a private chat.
+ Last name of the other party in a private chat, for private chats.
photo (:obj:`ChatPhoto `, *optional*):
Chat photo. Suitable for downloads only.
description (``str``, *optional*):
- Description, for supergroups and channel chats.
+ Bio, for private chats and bots or description for groups, supergroups and channels.
Returned only in :meth:`get_chat() `.
invite_link (``str``, *optional*):
- Chat invite link, for supergroups and channel chats.
+ Chat invite link, for groups, supergroups and channels.
Returned only in :meth:`get_chat() `.
pinned_message (:obj:`Message`, *optional*):
- Pinned message, for supergroups and channel chats.
+ Pinned message, for groups, supergroups channels and own chat.
Returned only in :meth:`get_chat() `.
sticker_set_name (``str``, *optional*):
@@ -71,13 +71,13 @@ class Chat(PyrogramType):
Returned only in :meth:`get_chat() `.
members_count (``int``, *optional*):
- Chat members count, for groups and channels only.
+ Chat members count, for groups, supergroups and channels only.
restriction_reason (``str``, *optional*):
The reason why this chat might be unavailable to some users.
permissions (:obj:`ChatPermissions ` *optional*):
- Information about the chat default permissions.
+ Information about the chat default permissions, for groups and supergroups.
"""
__slots__ = [
@@ -128,7 +128,7 @@ def __init__(
def _parse_user_chat(client, user: types.User) -> "Chat":
return Chat(
id=user.id,
- type="private",
+ type="bot" if user.bot else "private",
username=user.username,
first_name=user.first_name,
last_name=user.last_name,
From b6ea451ee5db710fdf562c18cba11c37f1f67b7a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 17 May 2019 13:44:44 +0200
Subject: [PATCH 0148/1652] Reword some method return type docstrings
---
docs/source/api/handlers.rst | 16 ----------------
docs/source/api/methods.rst | 2 --
docs/source/api/types.rst | 2 ++
docs/source/index.rst | 2 +-
docs/source/intro/start.rst | 2 +-
docs/source/topics/advanced-usage.rst | 2 +-
.../client/methods/bots/answer_inline_query.py | 2 +-
.../methods/bots/send_inline_bot_result.py | 2 +-
pyrogram/client/methods/bots/set_game_score.py | 6 +++---
.../methods/chats/get_chat_members_count.py | 2 +-
.../client/methods/chats/get_dialogs_count.py | 2 +-
.../client/methods/chats/kick_chat_member.py | 5 ++---
pyrogram/client/methods/contacts/get_contacts.py | 1 +
.../methods/contacts/get_contacts_count.py | 2 +-
.../client/methods/messages/download_media.py | 5 ++---
.../messages/edit_message_reply_markup.py | 5 ++---
.../client/methods/messages/forward_messages.py | 8 +++-----
.../client/methods/messages/get_history_count.py | 2 +-
pyrogram/client/methods/messages/get_messages.py | 8 +++-----
.../client/methods/messages/send_animation.py | 5 ++---
pyrogram/client/methods/messages/send_audio.py | 5 ++---
.../client/methods/messages/send_document.py | 5 ++---
pyrogram/client/methods/messages/send_photo.py | 5 ++---
pyrogram/client/methods/messages/send_sticker.py | 6 ++----
pyrogram/client/methods/messages/send_video.py | 5 ++---
.../client/methods/messages/send_video_note.py | 5 ++---
pyrogram/client/methods/messages/send_voice.py | 5 ++---
.../users/get_user_profile_photos_count.py | 2 +-
pyrogram/client/methods/users/get_users.py | 7 ++++---
29 files changed, 48 insertions(+), 78 deletions(-)
diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst
index 023f6e28b53..5f80922da58 100644
--- a/docs/source/api/handlers.rst
+++ b/docs/source/api/handlers.rst
@@ -38,26 +38,10 @@ it.
RawUpdateHandler
.. autoclass:: MessageHandler()
- :members:
-
.. autoclass:: DeletedMessagesHandler()
- :members:
-
.. autoclass:: CallbackQueryHandler()
- :members:
-
.. autoclass:: InlineQueryHandler()
- :members:
-
.. autoclass:: UserStatusHandler()
- :members:
-
.. autoclass:: PollHandler()
- :members:
-
.. autoclass:: DisconnectHandler()
- :members:
-
.. autoclass:: RawUpdateHandler()
- :members:
-
diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst
index ded4d017bb3..7c061d3a3d7 100644
--- a/docs/source/api/methods.rst
+++ b/docs/source/api/methods.rst
@@ -90,7 +90,6 @@ Chats
pin_chat_message
unpin_chat_message
get_chat
- get_chat_preview
get_chat_member
get_chat_members
get_chat_members_count
@@ -217,7 +216,6 @@ Bots
.. automethod:: pyrogram.Client.pin_chat_message()
.. automethod:: pyrogram.Client.unpin_chat_message()
.. automethod:: pyrogram.Client.get_chat()
-.. automethod:: pyrogram.Client.get_chat_preview()
.. automethod:: pyrogram.Client.get_chat_member()
.. automethod:: pyrogram.Client.get_chat_members()
.. automethod:: pyrogram.Client.get_chat_members_count()
diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst
index 506fe003f83..d911520c6e4 100644
--- a/docs/source/api/types.rst
+++ b/docs/source/api/types.rst
@@ -72,6 +72,7 @@ Keyboards
ForceReply
CallbackQuery
GameHighScore
+ GameHighScores
CallbackGame
Input Media
@@ -155,6 +156,7 @@ InputMessageContent
.. autoclass:: ForceReply()
.. autoclass:: CallbackQuery()
.. autoclass:: GameHighScore()
+.. autoclass:: GameHighScores()
.. autoclass:: CallbackGame()
.. Input Media
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 9b878bafc29..d061b67715f 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -17,7 +17,7 @@ Welcome to Pyrogram
GitHub
•
-
+
Community
diff --git a/docs/source/intro/start.rst b/docs/source/intro/start.rst
index 2fcd46331ff..c7bfc74a4dc 100644
--- a/docs/source/intro/start.rst
+++ b/docs/source/intro/start.rst
@@ -44,5 +44,5 @@ In the next few pages of the introduction, we'll take a much more in-depth look
Feeling eager to continue? You can take a shortcut to `Calling Methods`_ and come back later to learn some more details.
-.. _community: //t.me/pyrogramchat
+.. _community: //t.me/Pyrogram
.. _Calling Methods: ../start/invoking
\ No newline at end of file
diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst
index 02a3e3b62ec..4032783f213 100644
--- a/docs/source/topics/advanced-usage.rst
+++ b/docs/source/topics/advanced-usage.rst
@@ -130,4 +130,4 @@ high-level method.
.. _plenty of them: ../api/methods
.. _raw functions: ../telegram/functions
.. _raw types: ../telegram/types
-.. _Community: https://t.me/PyrogramChat
\ No newline at end of file
+.. _Community: https://t.me/Pyrogram
\ No newline at end of file
diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py
index 62344f20470..38ed99c3a7b 100644
--- a/pyrogram/client/methods/bots/answer_inline_query.py
+++ b/pyrogram/client/methods/bots/answer_inline_query.py
@@ -73,7 +73,7 @@ def answer_inline_query(
where they wanted to use the bot's inline capabilities.
Returns:
- ``bool``: On success, True is returned.
+ ``bool``: True, on success.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py
index 031591db4dc..411ab462258 100644
--- a/pyrogram/client/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/client/methods/bots/send_inline_bot_result.py
@@ -58,7 +58,7 @@ def send_inline_bot_result(
Sends the message with *via @bot* hidden.
Returns:
- On success, the sent Message is returned.
+ :obj:`Message`: On success, the sent inline result message is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py
index 3b0e97e23d8..f9115b74be6 100644
--- a/pyrogram/client/methods/bots/set_game_score.py
+++ b/pyrogram/client/methods/bots/set_game_score.py
@@ -32,7 +32,7 @@ def set_game_score(
disable_edit_message: bool = None,
chat_id: Union[int, str] = None,
message_id: int = None
- ):
+ ) -> Union["pyrogram.Message", bool]:
# inline_message_id: str = None): TODO Add inline_message_id
"""Set the score of the specified user in a game.
@@ -63,8 +63,8 @@ def set_game_score(
Required if inline_message_id is not specified.
Returns:
- On success, if the message was sent by the bot, returns the edited :obj:`Message`,
- otherwise returns True.
+ :obj:`Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned,
+ True otherwise.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py
index c8bd6deb79e..4c7ab747953 100644
--- a/pyrogram/client/methods/chats/get_chat_members_count.py
+++ b/pyrogram/client/methods/chats/get_chat_members_count.py
@@ -34,7 +34,7 @@ def get_chat_members_count(
Unique identifier (int) or username (str) of the target chat.
Returns:
- On success, an integer is returned.
+ ``int``: On success, the chat members count is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py
index 1a307433f98..b9b0970aade 100644
--- a/pyrogram/client/methods/chats/get_dialogs_count.py
+++ b/pyrogram/client/methods/chats/get_dialogs_count.py
@@ -29,7 +29,7 @@ def get_dialogs_count(self, pinned_only: bool = False) -> int:
Defaults to False.
Returns:
- ``int``: On success, an integer is returned.
+ ``int``: On success, the dialogs count is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py
index f50588290c0..9686e754e54 100644
--- a/pyrogram/client/methods/chats/kick_chat_member.py
+++ b/pyrogram/client/methods/chats/kick_chat_member.py
@@ -54,9 +54,8 @@ def kick_chat_member(
considered to be banned forever. Defaults to 0 (ban forever).
Returns:
- :obj:`Message`: On success, a service message will be returned (when applicable).
-
- ``bool`` -- True, in case a message object couldn't be returned.
+ :obj:`Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in
+ case a message object couldn't be returned, True is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py
index 1fa5b7389d6..0c2316707c7 100644
--- a/pyrogram/client/methods/contacts/get_contacts.py
+++ b/pyrogram/client/methods/contacts/get_contacts.py
@@ -30,6 +30,7 @@
class GetContacts(BaseClient):
def get_contacts(self) -> List["pyrogram.User"]:
+ # TODO: Create a Users object and return that
"""Get contacts from your Telegram address book.
Returns:
diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py
index 01fb0789b6a..dddfe8c40ea 100644
--- a/pyrogram/client/methods/contacts/get_contacts_count.py
+++ b/pyrogram/client/methods/contacts/get_contacts_count.py
@@ -25,7 +25,7 @@ def get_contacts_count(self) -> int:
"""Get the total count of contacts from your Telegram address book.
Returns:
- ``int``: On success, an integer is returned.
+ ``int``: On success, the contacts count is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py
index 5c4401739d7..c21a95bfa3e 100644
--- a/pyrogram/client/methods/messages/download_media.py
+++ b/pyrogram/client/methods/messages/download_media.py
@@ -73,9 +73,8 @@ def download_media(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- ``str``: On success, the absolute path of the downloaded file is returned.
-
- ``None`` -- In case the download is deliberately stopped with :meth:`stop_transmission`.
+ ``str`` | ``None``: On success, the absolute path of the downloaded file is returned, otherwise, in case
+ the download failed or was deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py
index 8d2b82af93e..a058646f2e2 100644
--- a/pyrogram/client/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py
@@ -45,9 +45,8 @@ def edit_message_reply_markup(
An InlineKeyboardMarkup object.
Returns:
- :obj:`Message`: In case the edited message is sent by the bot.
-
- ``bool`` -- True, in case the edited message is sent by the user.
+ :obj:`Message` | ``bool``: In case the edited message is sent by the bot, the edited message is returned,
+ otherwise, True is returned in case the edited message is send by the user.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py
index 82c35f92624..bc9ad33124c 100644
--- a/pyrogram/client/methods/messages/forward_messages.py
+++ b/pyrogram/client/methods/messages/forward_messages.py
@@ -64,11 +64,9 @@ def forward_messages(
Defaults to False.
Returns:
- :obj:`Message`: In case *message_ids* was an integer, the single forwarded message is
- returned.
-
- :obj:`Messages` -- In case *message_ids* was an iterable, the returned value will be an
- object containing a list of messages, even if such iterable contained just a single element.
+ :obj:`Message` | :obj:`Messages`: In case *message_ids* was an integer, the single forwarded message is
+ returned, otherwise, in case *message_ids* was an iterable, the returned value will be an object containing
+ a list of messages, even if such iterable contained just a single element.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py
index ced46799038..486140300b5 100644
--- a/pyrogram/client/methods/messages/get_history_count.py
+++ b/pyrogram/client/methods/messages/get_history_count.py
@@ -45,7 +45,7 @@ def get_history_count(
Unique identifier (int) or username (str) of the target chat.
Returns:
- ``int``: On success, an integer is returned.
+ ``int``: On success, the chat history count is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py
index 51f7135298d..7a60f276924 100644
--- a/pyrogram/client/methods/messages/get_messages.py
+++ b/pyrogram/client/methods/messages/get_messages.py
@@ -60,11 +60,9 @@ def get_messages(
Defaults to 1.
Returns:
- :obj:`Message`: In case *message_ids* was an integer, the single forwarded message is
- returned.
-
- :obj:`Messages` -- In case *message_ids* was an iterable, the returned value will be an
- object containing a list of messages, even if such iterable contained just a single element.
+ :obj:`Message` | :obj:`Messages`: In case *message_ids* was an integer, the single requested message is
+ returned, otherwise, in case *message_ids* was an iterable, the returned value will be an object containing
+ a list of messages, even if such iterable contained just a single element.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py
index 461ef6fc96d..edf5ea54236 100644
--- a/pyrogram/client/methods/messages/send_animation.py
+++ b/pyrogram/client/methods/messages/send_animation.py
@@ -120,9 +120,8 @@ def send_animation(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent animation message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent animation message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py
index aaa5a529497..b6d7b77716e 100644
--- a/pyrogram/client/methods/messages/send_audio.py
+++ b/pyrogram/client/methods/messages/send_audio.py
@@ -122,9 +122,8 @@ def send_audio(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent audio message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent audio message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py
index 66b3f1c90dc..df202fd7121 100644
--- a/pyrogram/client/methods/messages/send_document.py
+++ b/pyrogram/client/methods/messages/send_document.py
@@ -108,9 +108,8 @@ def send_document(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent document message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent document message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py
index 7c4f688fbe9..14292da9b42 100644
--- a/pyrogram/client/methods/messages/send_photo.py
+++ b/pyrogram/client/methods/messages/send_photo.py
@@ -107,9 +107,8 @@ def send_photo(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent photo message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent photo message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py
index cabe3487cca..4a03e4c8d17 100644
--- a/pyrogram/client/methods/messages/send_sticker.py
+++ b/pyrogram/client/methods/messages/send_sticker.py
@@ -92,10 +92,8 @@ def send_sticker(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent sticker message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
-
+ :obj:`Message` | ``None``: On success, the sent sticker message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
"""
diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py
index 92066e19d1a..d2d5afc0099 100644
--- a/pyrogram/client/methods/messages/send_video.py
+++ b/pyrogram/client/methods/messages/send_video.py
@@ -124,9 +124,8 @@ def send_video(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent video message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent video message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py
index 8886c58817c..c17ea92a948 100644
--- a/pyrogram/client/methods/messages/send_video_note.py
+++ b/pyrogram/client/methods/messages/send_video_note.py
@@ -107,9 +107,8 @@ def send_video_note(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent video note message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent video note message is returned, otherwise, in case the
+ pload is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py
index 3631d828817..59031f5f16b 100644
--- a/pyrogram/client/methods/messages/send_voice.py
+++ b/pyrogram/client/methods/messages/send_voice.py
@@ -105,9 +105,8 @@ def send_voice(
You can either keep *\*args* or add every single extra argument in your function signature.
Returns:
- :obj:`Message`: On success, the sent voice message is returned.
-
- ``None`` -- In case the upload is deliberately stopped with :meth:`stop_transmission`.
+ :obj:`Message` | ``None``: On success, the sent voice message is returned, otherwise, in case the upload
+ is deliberately stopped with :meth:`stop_transmission`, None is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/users/get_user_profile_photos_count.py b/pyrogram/client/methods/users/get_user_profile_photos_count.py
index f552658400b..7870d00318c 100644
--- a/pyrogram/client/methods/users/get_user_profile_photos_count.py
+++ b/pyrogram/client/methods/users/get_user_profile_photos_count.py
@@ -33,7 +33,7 @@ def get_user_profile_photos_count(self, user_id: Union[int, str]) -> int:
For a contact that exists in your Telegram address book you can use his phone number (str).
Returns:
- ``int``: On success, an integer is returned.
+ ``int``: On success, the user profile photos count is returned.
Raises:
RPCError: In case of a Telegram RPC error.
diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py
index e95fedc783a..4ec0e893a6e 100644
--- a/pyrogram/client/methods/users/get_users.py
+++ b/pyrogram/client/methods/users/get_users.py
@@ -24,6 +24,7 @@
class GetUsers(BaseClient):
+ # TODO: Add Users type and use that
def get_users(
self,
user_ids: Union[Iterable[Union[int, str]], int, str]
@@ -38,9 +39,9 @@ def get_users(
Iterators and Generators are also accepted.
Returns:
- :obj:`User`: In case *user_ids* was an integer or string.
-
- List of :obj:`User` -- In case *user_ids* was an iterable, even if the iterable contained one item only.
+ :obj:`User` | List of :obj:`User`: In case *user_ids* was an integer or string the single requested user is
+ returned, otherwise, in case *user_ids* was an iterable a list of users is returned, even if the iterable
+ contained one item only.
Raises:
RPCError: In case of a Telegram RPC error.
From 518220431ecf25ee987bee30f5bff78aa7045510 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 18 May 2019 01:45:01 +0200
Subject: [PATCH 0149/1652] Docs revamp. Part 5
---
README.md | 1 +
docs/source/index.rst | 33 ++++-------
docs/source/intro/auth.rst | 4 +-
docs/source/intro/install.rst | 4 +-
docs/source/intro/start.rst | 3 +-
docs/source/start/errors.rst | 33 ++++++++++-
docs/source/start/invoking.rst | 2 +-
docs/source/start/updates.rst | 12 ++--
docs/source/topics/faq.rst | 79 +++++++++++++++++++------
docs/source/topics/glossary.rst | 23 ++++++-
docs/source/topics/session-settings.rst | 10 ++--
pyrogram/__init__.py | 4 +-
pyrogram/client/client.py | 2 +-
pyrogram/client/ext/base_client.py | 2 +-
14 files changed, 144 insertions(+), 68 deletions(-)
diff --git a/README.md b/README.md
index 4d202b4f0f8..6e12bce0b37 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,7 @@ ground up in Python and C. It enables you to easily create custom apps for both
- **Documented**: Pyrogram API methods, types and public interfaces are well documented.
- **Type-hinted**: Exposed Pyrogram types and method parameters are all type-hinted.
- **Updated**, to make use of the latest Telegram API version and features.
+- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed.
- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code.
- **Comprehensive**: Execute any advanced action an official client is able to do, and even more.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index d061b67715f..a68c81d982b 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -14,29 +14,15 @@ Welcome to Pyrogram
- GitHub
+ Source Code
- •
-
- Community
-
-
•
Releases
•
-
- PyPI
-
-
-
-
-
-
-
+
+ Community
@@ -59,7 +45,7 @@ C. It enables you to easily create custom apps for both user and bot identities
`MTProto API`_.
.. _Telegram: https://telegram.org
-.. _MTProto API: https://core.telegram.org/api#telegram-api
+.. _MTProto API: topics/faq#what-can-mtproto-do-more-than-the-bot-api
How the Documentation is Organized
----------------------------------
@@ -68,11 +54,11 @@ Contents are organized into self-contained topics and can be all accessed from t
order using the Next button at the end of each page. Here below you can, instead, find a list of the most relevant
pages for a quick access.
-Getting Started
-^^^^^^^^^^^^^^^
+First Steps
+^^^^^^^^^^^
-- `Quick Start`_ - Overview to get you started as fast as possible.
-- `Calling Methods`_ - How to use Pyrogram's API.
+- `Quick Start`_ - Overview to get you started quickly.
+- `Calling Methods`_ - How to call Pyrogram's methods.
- `Handling Updates`_ - How to handle Telegram updates.
- `Error Handling`_ - How to handle API errors correctly.
@@ -83,7 +69,8 @@ Getting Started
API Reference
^^^^^^^^^^^^^
-- `Client Class`_ - Details about the Client class.
+
+- `Client Class`_ - Reference details about the Client class.
- `Available Methods`_ - A list of available high-level methods.
- `Available Types`_ - A list of available high-level types.
- `Bound Methods`_ - A list of convenient bound methods.
diff --git a/docs/source/intro/auth.rst b/docs/source/intro/auth.rst
index 846a19a151d..483d12022df 100644
--- a/docs/source/intro/auth.rst
+++ b/docs/source/intro/auth.rst
@@ -2,7 +2,7 @@ Authorization
=============
Once a `project is set up`_, you will still have to follow a few steps before you can actually use Pyrogram to make
-API calls. This section provides all the information you need in order to authorize yourself as user or a bot.
+API calls. This section provides all the information you need in order to authorize yourself as user or bot.
User Authorization
------------------
@@ -51,7 +51,7 @@ the `Bot Father`_. Bot tokens replace the users' phone numbers only — you stil
The authorization process is automatically managed. All you need to do is choose a ``session_name`` (can be anything,
usually your bot username) and pass your bot token using the ``bot_token`` parameter. The session file will be named
-after the session name, which will be ``pyrogrambot.session`` for the example below.
+after the session name, which will be ``my_bot.session`` for the example below.
.. code-block:: python
diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst
index c4df60d45be..f26fc37fdaa 100644
--- a/docs/source/intro/install.rst
+++ b/docs/source/intro/install.rst
@@ -55,7 +55,7 @@ Use this command to install (note "asyncio.zip" in the link):
$ pip3 install -U https://github.com/pyrogram/pyrogram/archive/asyncio.zip
-Pyrogram API remains the same and features are kept up to date from the non-async, default develop branch, but you
+Pyrogram's API remains the same and features are kept up to date from the non-async, default develop branch, but you
are obviously required Python asyncio knowledge in order to take full advantage of it.
@@ -87,7 +87,7 @@ If no error shows up you are good to go.
>>> import pyrogram
>>> pyrogram.__version__
- '0.12.0'
+ '0.13.0'
.. _TgCrypto: ../topics/tgcrypto
.. _`Github repo`: http://github.com/pyrogram/pyrogram
diff --git a/docs/source/intro/start.rst b/docs/source/intro/start.rst
index c7bfc74a4dc..1aa7989e64a 100644
--- a/docs/source/intro/start.rst
+++ b/docs/source/intro/start.rst
@@ -1,7 +1,8 @@
Quick Start
===========
-The next few steps serve as a quick start for all new Pyrogrammers that want to get something done as fast as possible!
+The next few steps serve as a quick start for all new Pyrogrammers that want to get something done as fast as possible.
+Let's go!
Get Pyrogram Real Fast
----------------------
diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst
index 1f4f5a2e251..cf329947661 100644
--- a/docs/source/start/errors.rst
+++ b/docs/source/start/errors.rst
@@ -11,8 +11,9 @@ to control the behaviour of your application. Pyrogram errors all live inside th
RPCError
--------
-The father of all errors is named ``RPCError``. This error exists in form of a Python exception and is able to catch all
-Telegram API related errors.
+The father of all errors is named ``RPCError``. This error exists in form of a Python exception which is directly
+subclass-ed from Python's main ``Exception`` and is able to catch all Telegram API related errors. This error is raised
+every time a method call against Telegram's API was unsuccessful.
.. code-block:: python
@@ -27,7 +28,7 @@ Error Categories
----------------
The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram
-provides categories of errors, which are named after the common HTTP errors:
+provides categories of errors, which are named after the common HTTP errors and subclass-ed from the RPCError:
.. code-block:: python
@@ -41,6 +42,32 @@ provides categories of errors, which are named after the common HTTP errors:
- `420 - Flood <../api/errors#flood>`_
- `500 - InternalServerError <../api/errors#internalservererror>`_
+Single Errors
+-------------
+
+For a fine-grained control over every single error, Pyrogram does also expose errors that deal each with a specific
+issue. For example:
+
+.. code-block:: python
+
+ from pyrogram.errors import FloodWait
+
+These errors subclass directly from the category of errors they belong to, which in turn subclass from the father
+RPCError, thus building a class of error hierarchy such as this:
+
+- RPCError
+ - BadRequest
+ - ``MessageEmpty``
+ - ``UsernameOccupied``
+ - ``...``
+ - InternalServerError
+ - ``RpcCallFail``
+ - ``InterDcCallError``
+ - ``...``
+ - ``...``
+
+.. _Errors: api/errors
+
Unknown Errors
--------------
diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst
index fae1952340a..ef9bc373f6c 100644
--- a/docs/source/start/invoking.rst
+++ b/docs/source/start/invoking.rst
@@ -36,7 +36,7 @@ Now instantiate a new Client object, "my_account" is a session name of your choi
app = Client("my_account")
-To actually make use of any method, the client has to be started:
+To actually make use of any method, the client has to be started first:
.. code-block:: python
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index 930096a3b2a..644cf31c951 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -9,11 +9,11 @@ Defining Updates
First, let's define what are these updates. As hinted already, updates are simply events that happen in your Telegram
account (incoming messages, new members join, bot button presses, etc...), which are meant to notify you about a new
-specific state that changed. These updates are handled by registering one or more callback functions in your app using
-`Handlers <../api/handlers>`_.
+specific state that has changed. These updates are handled by registering one or more callback functions in your app
+using `Handlers <../api/handlers>`_.
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
-function will be called and its body executed.
+function will be called back by the framework and its body executed.
Registering an Handler
----------------------
@@ -63,8 +63,8 @@ above must only handle updates that are in form of a :obj:`Message `. This method is used to actually register the
-handler and let Pyrogram know it needs to be taken into consideration when new updates arrive and the dispatching phase
-begins.
+handler and let Pyrogram know it needs to be taken into consideration when new updates arrive and the internal
+dispatching phase begins.
.. code-block:: python
@@ -109,4 +109,4 @@ to do so is by decorating your callback function with the :meth:`on_message() `_ with Pyrogram). They
store some useful information such as the client who's using them and from which country and IP address.
-.. figure:: https://i.imgur.com/lzGPCdZ.png
- :width: 70%
+.. figure:: https://i.imgur.com/YaqtMLO.png
+ :width: 90%
:align: center
- **A Pyrogram session running on Linux, Python 3.6.**
+ **A Pyrogram session running on Linux, Python 3.7.**
That's how a session looks like on the Android app, showing the three main pieces of information.
-- ``app_version``: **Pyrogram 🔥 0.7.5**
-- ``device_model``: **CPython 3.6.5**
+- ``app_version``: **Pyrogram 0.13.0**
+- ``device_model``: **CPython 3.7.2**
- ``system_version``: **Linux 4.15.0-23-generic**
Set Custom Values
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index bf688797d0a..07c460fc0da 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -26,9 +26,7 @@
__version__ = "0.13.0.develop"
__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"
-)
+__copyright__ = "Copyright (C) 2017-2019 Dan "
from .errors import RPCError
from .client import *
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index d382b2fb71d..247a29cbfef 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -78,7 +78,7 @@ class Client(Methods, BaseClient):
This is an alternative way to pass it if you don't want to use the *config.ini* file.
app_version (``str``, *optional*):
- Application version. Defaults to "Pyrogram :fire: vX.Y.Z"
+ Application version. Defaults to "Pyrogram X.Y.Z"
This is an alternative way to set it if you don't want to use the *config.ini* file.
device_model (``str``, *optional*):
diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py
index ee7b2c243c4..a3816bdfb51 100644
--- a/pyrogram/client/ext/base_client.py
+++ b/pyrogram/client/ext/base_client.py
@@ -31,7 +31,7 @@ class BaseClient:
class StopTransmission(StopIteration):
pass
- APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
+ APP_VERSION = "Pyrogram {}".format(__version__)
DEVICE_MODEL = "{} {}".format(
platform.python_implementation(),
From 578dab171c034beedc37437695b3d1646c18e17b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 18 May 2019 03:21:02 +0200
Subject: [PATCH 0150/1652] Docs revamp. Part 6
---
.../intro/{start.rst => quickstart.rst} | 0
docs/source/{topics => meta}/faq.rst | 73 ++++++++-----------
docs/source/{topics => meta}/glossary.rst | 19 +++--
docs/source/meta/powered-by.rst | 69 ++++++++++++++++++
docs/source/{topics => meta}/releases.rst | 0
docs/source/meta/support-pyrogram.rst | 2 +
docs/source/{intro => start}/auth.rst | 2 +-
docs/source/topics/mtproto-vs-botapi.rst | 58 +++++++++++++++
8 files changed, 174 insertions(+), 49 deletions(-)
rename docs/source/intro/{start.rst => quickstart.rst} (100%)
rename docs/source/{topics => meta}/faq.rst (73%)
rename docs/source/{topics => meta}/glossary.rst (79%)
create mode 100644 docs/source/meta/powered-by.rst
rename docs/source/{topics => meta}/releases.rst (100%)
create mode 100644 docs/source/meta/support-pyrogram.rst
rename docs/source/{intro => start}/auth.rst (98%)
create mode 100644 docs/source/topics/mtproto-vs-botapi.rst
diff --git a/docs/source/intro/start.rst b/docs/source/intro/quickstart.rst
similarity index 100%
rename from docs/source/intro/start.rst
rename to docs/source/intro/quickstart.rst
diff --git a/docs/source/topics/faq.rst b/docs/source/meta/faq.rst
similarity index 73%
rename from docs/source/topics/faq.rst
rename to docs/source/meta/faq.rst
index 10cc9543e97..07f4aece443 100644
--- a/docs/source/topics/faq.rst
+++ b/docs/source/meta/faq.rst
@@ -20,17 +20,17 @@ C. It enables you to easily create custom applications for both user and bot ide
`MTProto API`_ with the Python programming language.
.. _Telegram: https://telegram.org
-.. _MTProto API: https://core.telegram.org/api#telegram-api
+.. _MTProto API: ../topics/mtproto-vs-botapi#what-is-the-mtproto-api
-What does the name mean?
-------------------------
+Where does the name come from?
+------------------------------
-The word "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**,
+The name "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**,
from *Telegram*. The word *pyro* itself is built from *Python*, **py** for short, and the suffix **ro** to come up with
the word *fire*, which also inspired the project logo.
-How old is the project?
------------------------
+How old is Pyrogram?
+--------------------
Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior the
initial public release on `GitHub`_.
@@ -51,40 +51,15 @@ Why Pyrogram?
- **Comprehensive**: Execute any `advanced action`_ an official client is able to do, and even more.
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-.. _Smart Plugin: smart-plugins
-.. _advanced action: advanced-usage
+.. _Smart Plugin: ../topics/smart-plugins
+.. _advanced action: ../topics/advanced-usage
What can MTProto do more than the Bot API?
------------------------------------------
-Here you can find a list of all the known advantages in using MTProto-based libraries (Pyrogram) instead of the official
-HTTP Bot API:
+For a detailed answer, please refer to the `MTProto vs. Bot API`_ page.
-- **Authorize both user and bot identities**: The Bot API only allows bot accounts.
-
-- **Upload & download any file, up to 1500 MB each (~1.5 GB)**: The Bot API allows uploads and downloads of files only
- up to 50 MB / 20 MB in size (respectively).
-
-- **Has less overhead due to direct connections to Telegram**: The Bot API uses an intermediate server to handle HTTP
- requests before they are sent to the actual Telegram servers.
-
-- **Run multiple sessions at once, up to 10 per account (either bot or user)**: The Bot API intermediate server will
- terminate any other session in case you try to use the same bot again in a parallel connection.
-
-- **Get information about any public chat by usernames, even if not a member**: The Bot API simply doesn't support this.
-
-- **Obtain information about any message existing in a chat using their ids**: The Bot API simply doesn't support this.
-
-- **Retrieve the whole chat members list of either public or private chats**: The Bot API simply doesn't support this.
-
-- **Receive extra updates, such as the one about a user name change**: The Bot API simply doesn't support this.
-
-- **Has more meaningful errors in case something went wrong**: The Bot API reports less detailed errors.
-
-- **Has much more detailed types and powerful methods**: The Bot API types often miss some useful information about
- Telegram's type and some of the methods are limited as well.
-
-- **Get API version updates, and thus new features, sooner**: The Bot API is simply slower in implementing new features.
+.. _MTProto vs. Bot API: ../topics/mtproto-vs-botapi
Why do I need an API key for bots?
----------------------------------
@@ -126,7 +101,7 @@ fails or not:
|bug report|
-.. _you need a proxy: proxy
+.. _you need a proxy: ../topics/proxy
I keep getting PEER_ID_INVALID error!
-------------------------------------------
@@ -150,12 +125,27 @@ Can I use the same file_id across different accounts?
No, Telegram doesn't allow this.
-File ids are bound to a specific user/bot, and an attempt in using a foreign file id will result in errors such as
-**[400 MEDIA_EMPTY]: The media is invalid**.
+File ids are personal and bound to a specific user/bot -- and an attempt in using a foreign file id will result in
+errors such as **[400 MEDIA_EMPTY]: The media is invalid**.
The only exception are stickers' file ids; you can use them across different accounts without any problem, like this
one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``.
+Can I use Bot API's file_ids in Pyrogram?
+-----------------------------------------
+
+Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram...
+
+...at least for now.
+
+Telegram is slowly changing some server's internals and it's doing it in such a way that file ids are going to break
+inevitably. Not only this, but it seems that the new, hypothetical, file ids could also possibly expire at anytime, thus
+losing the *persistence* feature.
+
+This change will most likely affect the official `Bot API <../topics/mtproto-vs-botapi#what-is-the-bot-api>`_ too
+(unless Telegram implements some workarounds server-side to keep backwards compatibility, which Pyrogram could in turn
+make use of) and we can expect a proper notice from Telegram.
+
My account has been deactivated/limited!
----------------------------------------
@@ -184,13 +174,14 @@ About the License
.. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png
:align: left
-Pyrogram is free software and is currently licensed under the terms of the GNU Lesser General Public License v3 or later
-(LGPLv3+). In short: you may use, redistribute and/or modify it provided that modifications are described and licensed
-for free under LGPLv3+.
+Pyrogram is free software and is currently licensed under the terms of the
+`GNU Lesser General Public License v3 or later (LGPLv3+)`_. In short: you may use, redistribute and/or modify it
+provided that modifications are described and licensed for free under LGPLv3+.
In other words: you can use and integrate Pyrogram into your own code --- either open source, under the same or a
different licence or even proprietary --- without being required to release the source code of your own applications.
However, any modifications to the library itself are required to be published for free under the same LGPLv3+ license.
+.. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser
.. _Bug Report: https://github.com/pyrogram/pyrogram/issues/new?labels=bug&template=bug_report.md
.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md
diff --git a/docs/source/topics/glossary.rst b/docs/source/meta/glossary.rst
similarity index 79%
rename from docs/source/topics/glossary.rst
rename to docs/source/meta/glossary.rst
index 609185e784c..fb5bc8c164c 100644
--- a/docs/source/topics/glossary.rst
+++ b/docs/source/meta/glossary.rst
@@ -1,5 +1,5 @@
Pyrogram Glossary
------------------
+=================
This page contains a list of common words with brief explanations related to Pyrogram and, to some extent, Telegram in
general. Some words may as well link to dedicated articles in case the topic is covered in a more detailed fashion.
@@ -18,6 +18,7 @@ general. Some words may as well link to dedicated articles in case the topic is
API key
A secret code used to authenticate and/or authorize a specific application to Telegram in order for it to
control how the API is being used, for example, to prevent abuses of the API.
+ `More on API keys <../intro/setup#api-keys>`_.
DC
Also known as *data center*, is a place where lots of computer systems are housed and used together in order to
@@ -29,18 +30,21 @@ general. Some words may as well link to dedicated articles in case the topic is
RPCError
An error caused by an RPC which must be returned in place of the successful result in order to let the caller
- know something went wrong.
+ know something went wrong. `More on RPCError <../start/errors>`_.
MTProto
- The name of the custom-made, open encryption protocol by Telegram, implemented in Pyrogram.
+ The name of the custom-made, open and encrypted protocol by Telegram, implemented in Pyrogram.
+ `More on MTProto `_.
MTProto API
The Telegram main API Pyrogram makes use of, which is able to connect both users and normal bots to Telegram
- using MTProto as application layer protocol and execute any method Telegram provides from its public schema.
+ using MTProto as application layer protocol and execute any method Telegram provides from its public TL-schema.
+ `More on MTProto API `_.
Bot API
- The `Telegram Bot API`_ that is able to only connect normal bots to Telegram using HTTP as application layer
+ The Telegram Bot API that is able to only connect normal bots to Telegram using HTTP as application layer
protocol and allows to execute a subset of the main Telegram API.
+ `More on Bot API `_.
Pyrogrammer
A developer that uses Pyrogram to build Telegram applications.
@@ -61,12 +65,13 @@ general. Some words may as well link to dedicated articles in case the topic is
Handler
An object that wraps around a callback function that is *actually meant* to be registered into the framework,
which will then be able to handle a specific kind of events, such as a new incoming message, for example.
+ `More on Handlers <../start/updates>`_
Decorator
Also known as *function decorator*, in Python, is a callable object that is used to modify another function.
- Decorators in Pyrogram are used to automatically register callback functions for `handling updates`_.
+ Decorators in Pyrogram are used to automatically register callback functions for handling updates.
+ `More on Decorators <../start/updates#using-decorators>`_
-.. _Telegram Bot API: https://core.telegram.org/bots/api
.. _handling updates: ../start/updates
.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md
diff --git a/docs/source/meta/powered-by.rst b/docs/source/meta/powered-by.rst
new file mode 100644
index 00000000000..3e46fb07184
--- /dev/null
+++ b/docs/source/meta/powered-by.rst
@@ -0,0 +1,69 @@
+Powered by Pyrogram
+===================
+
+This is a collection of remarkable projects made with Pyrogram.
+
+.. A collection of Pyrojects :^)
+
+.. tip::
+
+ If you'd like to propose a project that's worth being listed here, feel free to open a `Feature Request`_.
+
+Projects Showcase
+-----------------
+
+`YTAudioBot `_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+| **A YouTube audio downloader on Telegram, serving over 200k MAU.**
+| --- by `Dan `_
+
+- Main: https://t.me/ytaudiobot
+- Mirror: https://t.me/ytaudio_bot
+- Website: https://ytaudiobot.ml
+
+-----
+
+`Pyrogram Assistant `_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+| **The assistant bot that helps people with Pyrogram directly on Telegram**
+| --- by `Dan `_
+
+- Bot: https://t.me/pyrogrambot
+- Source Code: https://github.com/pyrogram/assistant
+
+-----
+
+`PyroBot `_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+| **A Telegram userbot based on Pyrogram**
+| --- by `Colin `_
+
+- Source Code: https://git.colinshark.de/PyroBot/PyroBot
+
+-----
+
+`TgIntegration `_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+| **Integration Test Library for Telegram Messenger Bots in Python**
+| --- by `JosXa `_
+
+- Source Code: https://github.com/JosXa/tgintegration
+
+-----
+
+`BotListBot `_
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+| **A bot which partly uses Pyrogram to check if other bots are still alive**
+| --- by `JosXa `_
+
+- Source Code: https://github.com/JosXa/BotListBot
+
+-----
+
+.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md
+
diff --git a/docs/source/topics/releases.rst b/docs/source/meta/releases.rst
similarity index 100%
rename from docs/source/topics/releases.rst
rename to docs/source/meta/releases.rst
diff --git a/docs/source/meta/support-pyrogram.rst b/docs/source/meta/support-pyrogram.rst
new file mode 100644
index 00000000000..052d78bd678
--- /dev/null
+++ b/docs/source/meta/support-pyrogram.rst
@@ -0,0 +1,2 @@
+Support Pyrogram Development
+============================
diff --git a/docs/source/intro/auth.rst b/docs/source/start/auth.rst
similarity index 98%
rename from docs/source/intro/auth.rst
rename to docs/source/start/auth.rst
index 483d12022df..3f76e92987f 100644
--- a/docs/source/intro/auth.rst
+++ b/docs/source/start/auth.rst
@@ -64,6 +64,6 @@ after the session name, which will be ``my_bot.session`` for the example below.
app.run()
-.. _project is set up: setup.html
+.. _project is set up: ../intro/setup
.. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes
.. _Bot Father: https://t.me/botfather
\ No newline at end of file
diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst
new file mode 100644
index 00000000000..accebcd5517
--- /dev/null
+++ b/docs/source/topics/mtproto-vs-botapi.rst
@@ -0,0 +1,58 @@
+MTProto vs. Bot API
+===================
+
+Being Pyrogram an MTProto-based library, this very feature makes it already superior to, what is usually called, the
+official Bot API.
+
+What is the MTProto API?
+------------------------
+
+MTProto, took alone, is the name of the custom-made, open and encrypted communication protocol created by Telegram
+itself --- it's the only protocol used to exchange information between a client application and the actual Telegram
+servers.
+
+The MTProto **API** however, is what people, for convenience, call the main Telegram API as a whole. This API is able
+to authorize both users and bots and happens to be built on top of the MTProto encryption protocol by means of binary
+data serialized in a specific way, as described by the TL language, hence the correlation.
+
+What is the Bot API?
+--------------------
+
+The Bot API is an HTTP(S) interface for building normal bots. Bots are special accounts that are authorized via tokens
+instead of phone numbers. The Bot API is built yet again on top of the main Telegram API, but runs on an intermediate
+server application that in turn communicates with the actual Telegram servers using MTProto.
+
+.. figure:: https://i.imgur.com/C108qkX.png
+ :align: center
+
+Advantages of the MTProto API
+-----------------------------
+
+Here is a list of all the known advantages in using MTProto-based libraries (such as Pyrogram) instead of the official
+HTTP Bot API. Using Pyrogram you can:
+
+- **Authorize both user and bot identities**: The Bot API only allows bot accounts.
+
+- **Upload & download any file, up to 1500 MB each (~1.5 GB)**: The Bot API allows uploads and downloads of files only
+ up to 50 MB / 20 MB in size (respectively).
+
+- **Has less overhead due to direct connections to Telegram**: The Bot API uses an intermediate server to handle HTTP
+ requests before they are sent to the actual Telegram servers.
+
+- **Run multiple sessions at once, up to 10 per account (either bot or user)**: The Bot API intermediate server will
+ terminate any other session in case you try to use the same bot again in a parallel connection.
+
+- **Get information about any public chat by usernames, even if not a member**: The Bot API simply doesn't support this.
+
+- **Obtain information about any message existing in a chat using their ids**: The Bot API simply doesn't support this.
+
+- **Retrieve the whole chat members list of either public or private chats**: The Bot API simply doesn't support this.
+
+- **Receive extra updates, such as the one about a user name change**: The Bot API simply doesn't support this.
+
+- **Has more meaningful errors in case something went wrong**: The Bot API reports less detailed errors.
+
+- **Has much more detailed types and powerful methods**: The Bot API types often miss some useful information about
+ Telegram's type and some of the methods are limited as well.
+
+- **Get API version updates, and thus new features, sooner**: The Bot API is simply slower in implementing new features.
\ No newline at end of file
From 2032cec4d0d0f5e03283ff715557a62b81bb354d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 19 May 2019 21:38:11 +0200
Subject: [PATCH 0151/1652] Remove superfluous information
---
compiler/api/compiler.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 53b35929aa3..21348336bbf 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -336,7 +336,6 @@ def start():
if c.section == "functions":
docstring_args += "\n\n Returns:\n " + get_docstring_arg_type(c.return_type)
- docstring_args += "\n\n Raises:\n RPCError: In case of a Telegram RPC error."
else:
references = get_references(".".join(filter(None, [c.namespace, c.name])))
From 0dc953c32075d6a38ba945c1bd296f088b2695fd Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 19 May 2019 21:40:41 +0200
Subject: [PATCH 0152/1652] Update domain name references to pyrogram.org
---
README.md | 8 ++---
docs/source/conf.py | 2 +-
docs/source/index.rst | 44 +++++++++++++++-----------
docs/source/sitemap.py | 2 +-
docs/source/topics/text-formatting.rst | 8 ++---
examples/bot_keyboards.py | 2 +-
examples/inline_queries.py | 8 ++---
examples/welcomebot.py | 2 +-
pyrogram/client/client.py | 4 +--
pyrogram/crypto/aes.py | 2 +-
setup.py | 6 ++--
11 files changed, 48 insertions(+), 40 deletions(-)
diff --git a/README.md b/README.md
index 6e12bce0b37..017b513b0f9 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Telegram MTProto API Framework for Python
-
+
Documentation
•
@@ -66,7 +66,7 @@ ground up in Python and C. It enables you to easily create custom apps for both
### Requirements
- Python 3.4 or higher.
-- A [Telegram API key](https://docs.pyrogram.ml/intro/setup#api-keys).
+- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys).
### Installing
@@ -76,11 +76,11 @@ pip3 install pyrogram
### Resources
-- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
+- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.org.
- Reading [Examples in this repository](https://github.com/pyrogram/pyrogram/tree/master/examples) is also a good way
for learning how Pyrogram works.
- Seeking extra help? Don't be shy, come join and ask our [Community](https://t.me/PyrogramChat)!
-- For other requests you can send an [Email](mailto:admin@pyrogram.ml) or a [Message](https://t.me/haskell).
+- For other requests you can send an [Email](mailto:dan@pyrogram.org) or a [Message](https://t.me/haskell).
### Contributing
diff --git a/docs/source/conf.py b/docs/source/conf.py
index e08298ef5ba..5f073186a64 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -113,7 +113,7 @@
# documentation.
#
html_theme_options = {
- 'canonical_url': "https://docs.pyrogram.ml/",
+ 'canonical_url': "https://docs.pyrogram.org/",
'collapse_navigation': True,
'sticky_navigation': False,
'logo_only': True,
diff --git a/docs/source/index.rst b/docs/source/index.rst
index a68c81d982b..89c927196b1 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -4,7 +4,7 @@ Welcome to Pyrogram
.. raw:: html
@@ -45,7 +45,7 @@ C. It enables you to easily create custom apps for both user and bot identities
`MTProto API`_.
.. _Telegram: https://telegram.org
-.. _MTProto API: topics/faq#what-can-mtproto-do-more-than-the-bot-api
+.. _MTProto API: topics/mtproto-vs-botapi#what-is-the-mtproto-api
How the Documentation is Organized
----------------------------------
@@ -62,7 +62,7 @@ First Steps
- `Handling Updates`_ - How to handle Telegram updates.
- `Error Handling`_ - How to handle API errors correctly.
-.. _Quick Start: intro/start
+.. _Quick Start: intro/quickstart
.. _Calling Methods: start/invoking
.. _Handling Updates: start/updates
.. _Error Handling: start/errors
@@ -80,26 +80,26 @@ API Reference
.. _Available Types: api/types
.. _Bound Methods: api/bound-methods
-Relevant Topics
-^^^^^^^^^^^^^^^
+Meta
+^^^^
-- `Smart Plugins`_ - How to modularize your application.
-- `Advanced Usage`_ - How to use Telegram's raw API.
-- `Release Notes`_ - Release notes for Pyrogram releases.
- `Pyrogram FAQ`_ - Answers to common Pyrogram questions.
- `Pyrogram Glossary`_ - A list of words with brief explanations.
+- `Release Notes`_ - Release notes for Pyrogram releases.
+- `Powered by Pyrogram`_ - A collection of Pyrogram Projects.
+- `Support Pyrogram Development`_ - Ways to show your appreciation.
-.. _Smart Plugins: topics/smart-plugins
-.. _Advanced Usage: topics/advanced-usage
-.. _Release Notes: topics/releases
-.. _Pyrogram FAQ: topics/faq
-.. _Pyrogram Glossary: topics/glossary
+.. _Pyrogram FAQ: meta/faq
+.. _Pyrogram Glossary: meta/glossary
+.. _Release Notes: meta/releases
+.. _Powered by Pyrogram: meta/powered-by
+.. _Support Pyrogram Development: meta/support-pyrogram
.. toctree::
:hidden:
:caption: Introduction
- intro/start
+ intro/quickstart
intro/install
intro/setup
@@ -107,7 +107,7 @@ Relevant Topics
:hidden:
:caption: Getting Started
- intro/auth
+ start/auth
start/invoking
start/updates
start/errors
@@ -139,12 +139,20 @@ Relevant Topics
topics/text-formatting
topics/proxy
topics/bots-interaction
+ topics/mtproto-vs-botapi
topics/test-servers
topics/advanced-usage
topics/voice-calls
- topics/releases
- topics/faq
- topics/glossary
+
+.. toctree::
+ :hidden:
+ :caption: Meta
+
+ meta/faq
+ meta/glossary
+ meta/releases
+ meta/powered-by
+ meta/support-pyrogram
.. toctree::
:hidden:
diff --git a/docs/source/sitemap.py b/docs/source/sitemap.py
index 539bac0d15f..b4d24c6a148 100644
--- a/docs/source/sitemap.py
+++ b/docs/source/sitemap.py
@@ -20,7 +20,7 @@
import os
import re
-canonical = "https://docs.pyrogram.ml"
+canonical = "https://docs.pyrogram.org/"
dirs = {
"start": ("weekly", 0.9),
diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst
index 535fec310ae..8f2292d0e6e 100644
--- a/docs/source/topics/text-formatting.rst
+++ b/docs/source/topics/text-formatting.rst
@@ -20,7 +20,7 @@ To use this mode, pass "markdown" in the *parse_mode* field when using
__italic text__
- [inline URL](https://docs.pyrogram.ml/)
+ [inline URL](https://docs.pyrogram.org/)
[inline mention of a user](tg://user?id=23122162)
@@ -43,7 +43,7 @@ The following tags are currently supported:
italic, italic
- inline URL
+ inline URL
inline mention of a user
@@ -66,7 +66,7 @@ Examples
"**bold**, "
"__italic__, "
"[mention](tg://user?id=23122162), "
- "[URL](https://docs.pyrogram.ml), "
+ "[URL](https://docs.pyrogram.org), "
"`code`, "
"```"
"for i in range(10):\n"
@@ -84,7 +84,7 @@ Examples
"bold, "
"italic, "
"mention, "
- "URL, "
+ "URL, "
"code, "
""
"for i in range(10):\n"
diff --git a/examples/bot_keyboards.py b/examples/bot_keyboards.py
index 4cbe8eaab02..e1ff1e7e3fd 100644
--- a/examples/bot_keyboards.py
+++ b/examples/bot_keyboards.py
@@ -39,7 +39,7 @@
),
InlineKeyboardButton( # Opens a web URL
"URL",
- url="https://docs.pyrogram.ml"
+ url="https://docs.pyrogram.org"
),
],
[ # Second row
diff --git a/examples/inline_queries.py b/examples/inline_queries.py
index c1727fe6a91..d86d90d5c79 100644
--- a/examples/inline_queries.py
+++ b/examples/inline_queries.py
@@ -22,12 +22,12 @@ def answer(client, inline_query):
input_message_content=InputTextMessageContent(
"Here's how to install **Pyrogram**"
),
- url="https://docs.pyrogram.ml/start/Installation",
+ url="https://docs.pyrogram.org/intro/install",
description="How to install Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
- [InlineKeyboardButton("Open website", url="https://docs.pyrogram.ml/start/Installation")]
+ [InlineKeyboardButton("Open website", url="https://docs.pyrogram.org/intro/install")]
]
)
),
@@ -37,12 +37,12 @@ def answer(client, inline_query):
input_message_content=InputTextMessageContent(
"Here's how to use **Pyrogram**"
),
- url="https://docs.pyrogram.ml/start/Usage",
+ url="https://docs.pyrogram.org/start/invoking",
description="How to use Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
- [InlineKeyboardButton("Open website", url="https://docs.pyrogram.ml/start/Usage")]
+ [InlineKeyboardButton("Open website", url="https://docs.pyrogram.org/start/invoking")]
]
)
)
diff --git a/examples/welcomebot.py b/examples/welcomebot.py
index ab252672017..35f72afff81 100644
--- a/examples/welcomebot.py
+++ b/examples/welcomebot.py
@@ -8,7 +8,7 @@
TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MENTION = "[{}](tg://user?id={})" # User mention markup
-MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" # Welcome message
+MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message
app = Client("my_account")
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 247a29cbfef..1cfa7c79efe 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -275,7 +275,7 @@ def start(self):
log.warning('\nWARNING: You are using a bot token as session name!\n'
'This usage will be deprecated soon. Please use a session file name to load '
'an existing session and the bot_token argument to create new sessions.\n'
- 'More info: https://docs.pyrogram.ml/start/Setup#bot-authorization\n')
+ 'More info: https://docs.pyrogram.org/intro/auth#bot-authorization\n')
self.load_config()
self.load_session()
@@ -1091,7 +1091,7 @@ def load_config(self):
else:
raise AttributeError(
"No API Key found. "
- "More info: https://docs.pyrogram.ml/intro/setup#configuration"
+ "More info: https://docs.pyrogram.org/intro/setup#configuration"
)
for option in ["app_version", "device_model", "system_version", "lang_code"]:
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index de275bd0f4d..d603caa08f7 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -56,7 +56,7 @@ def xor(a: bytes, b: bytes) -> bytes:
log.warning(
"TgCrypto is missing! "
"Pyrogram will work the same, but at a much slower speed. "
- "More info: https://docs.pyrogram.ml/resources/TgCrypto"
+ "More info: https://docs.pyrogram.org/topics/tgcrypto"
)
diff --git a/setup.py b/setup.py
index 245655e690a..f0d6d0303cc 100644
--- a/setup.py
+++ b/setup.py
@@ -126,13 +126,13 @@ def run(self):
setup(
name="Pyrogram",
version=version,
- description="Telegram MTProto API Client Library for Python",
+ description="Telegram MTProto API Client Library and Framework for Python",
long_description=readme,
long_description_content_type="text/markdown",
url="https://github.com/pyrogram",
download_url="https://github.com/pyrogram/pyrogram/releases/latest",
author="Dan Tès",
- author_email="admin@pyrogram.ml",
+ author_email="dan@pyrogram.org",
license="LGPLv3+",
classifiers=[
"Development Status :: 4 - Beta",
@@ -162,7 +162,7 @@ def run(self):
"Tracker": "https://github.com/pyrogram/pyrogram/issues",
"Community": "https://t.me/PyrogramChat",
"Source": "https://github.com/pyrogram/pyrogram",
- "Documentation": "https://docs.pyrogram.ml",
+ "Documentation": "https://docs.pyrogram.org",
},
python_requires="~=3.4",
packages=find_packages(exclude=["compiler*"]),
From a6b4a3fe4b0d68251f0bcd7dfaa0fbf824d2fbde Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 20 May 2019 17:59:04 +0200
Subject: [PATCH 0153/1652] Update email address
---
.github/CODE_OF_CONDUCT.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
index 27bb02a37dc..c3b69284152 100644
--- a/.github/CODE_OF_CONDUCT.md
+++ b/.github/CODE_OF_CONDUCT.md
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at admin@pyrogram.ml. All
+reported by contacting the project team at dan@pyrogram.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
From 5975a410902ee440edb770ebbfd87005f5ecdd41 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 20 May 2019 17:59:29 +0200
Subject: [PATCH 0154/1652] Bump pysocks version to 1.7.0
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 227aacf66a8..45525022943 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
pyaes==1.6.1
-pysocks==1.6.8
+pysocks==1.7.0
typing==3.6.6; python_version<"3.5"
\ No newline at end of file
From 8866a749e0fa764969a088260f7c0e906ddf7e84 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 20 May 2019 19:19:26 +0200
Subject: [PATCH 0155/1652] Fix spelling: an handler -> a handler Thanks
@rastamanjohn for the hint
---
docs/source/api/handlers.rst | 2 +-
docs/source/start/updates.rst | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst
index 5f80922da58..90c8e61414f 100644
--- a/docs/source/api/handlers.rst
+++ b/docs/source/api/handlers.rst
@@ -4,7 +4,7 @@ Update Handlers
Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions.
For a much more convenient way of registering callback functions have a look at `Decorators `_ instead.
-In case you decided to manually create an handler, use :meth:`add_handler() ` to register
+In case you decided to manually create a handler, use :meth:`add_handler() ` to register
it.
.. code-block:: python
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index 644cf31c951..a0f2ca0c6ee 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -15,8 +15,8 @@ using `Handlers <../api/handlers>`_.
Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback
function will be called back by the framework and its body executed.
-Registering an Handler
-----------------------
+Registering a Handler
+---------------------
To explain how handlers work let's have a look at the most used one, the
:obj:`MessageHandler `, which will be in charge for handling :obj:`Message `
From 79a8cefe5deae972ba086e9b29f50b4c925c779c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 21 May 2019 14:25:13 +0200
Subject: [PATCH 0156/1652] Add USER_BANNED_IN_CHANNEL error
---
compiler/error/source/400_BAD_REQUEST.tsv | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv
index db2f0e58f54..cbea977ad78 100644
--- a/compiler/error/source/400_BAD_REQUEST.tsv
+++ b/compiler/error/source/400_BAD_REQUEST.tsv
@@ -97,4 +97,5 @@ CHAT_NOT_MODIFIED The chat settings were not modified
RESULTS_TOO_MUCH The result contains too many items
RESULT_ID_DUPLICATE The result contains items with duplicated identifiers
ACCESS_TOKEN_INVALID The bot access token is invalid
-INVITE_HASH_EXPIRED The chat invite link is no longer valid
\ No newline at end of file
+INVITE_HASH_EXPIRED The chat invite link is no longer valid
+USER_BANNED_IN_CHANNEL You are limited, check @SpamBot for details
\ No newline at end of file
From 375aa85505a0c1bbc98b31bdc13bc2c722d9487d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 22 May 2019 03:33:54 +0200
Subject: [PATCH 0157/1652] Update sitemap.py
---
docs/{source => }/sitemap.py | 44 +++++++++++++++++++++++-------------
1 file changed, 28 insertions(+), 16 deletions(-)
rename docs/{source => }/sitemap.py (64%)
diff --git a/docs/source/sitemap.py b/docs/sitemap.py
similarity index 64%
rename from docs/source/sitemap.py
rename to docs/sitemap.py
index b4d24c6a148..87c2784931a 100644
--- a/docs/source/sitemap.py
+++ b/docs/sitemap.py
@@ -18,17 +18,16 @@
import datetime
import os
-import re
canonical = "https://docs.pyrogram.org/"
dirs = {
- "start": ("weekly", 0.9),
- "resources": ("weekly", 0.8),
- "pyrogram": ("weekly", 0.8),
- "functions": ("monthly", 0.7),
- "types": ("monthly", 0.7),
- "errors": ("weekly", 0.6)
+ ".": ("weekly", 1.0),
+ "intro": ("weekly", 0.8),
+ "start": ("weekly", 0.8),
+ "api": ("weekly", 0.6),
+ "topics": ("weekly", 0.6),
+ "telegram": ("weekly", 0.4)
}
@@ -37,10 +36,10 @@ def now():
with open("sitemap.xml", "w") as f:
- f.write("\n")
- f.write("\n")
+ f.write('\n')
+ f.write('\n')
- urls = [(canonical, now(), "weekly", 1.0)]
+ urls = []
def search(path):
@@ -48,14 +47,27 @@ def search(path):
for j in os.listdir(path):
search("{}/{}".format(path, j))
except NotADirectoryError:
- d = path.split("/")[0]
- path = "{}/{}".format(canonical, path.split(".")[0])
- path = re.sub("^(.+)/index$", "\g<1>", path)
- urls.append((path, now(), dirs[d][0], dirs[d][1]))
+ if not path.endswith(".rst"):
+ return
+ path = path.split("/")[1:]
- for i in dirs.keys():
- search(i)
+ if path[0].endswith(".rst"):
+ folder = "."
+ else:
+ folder = path[0]
+
+ path = "{}{}".format(canonical, "/".join(path))[:-len(".rst")]
+
+ if path.endswith("index"):
+ path = path[:-len("index")]
+
+ urls.append((path, now(), *dirs[folder]))
+
+
+ search("source")
+
+ urls.sort(key=lambda x: x[3], reverse=True)
for i in urls:
f.write(" \n")
From be612a498b834cbcc1762c4cbdbe43165dd3c794 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Wed, 22 May 2019 03:34:13 +0200
Subject: [PATCH 0158/1652] Add robots.txt
---
docs/robots.txt | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 docs/robots.txt
diff --git a/docs/robots.txt b/docs/robots.txt
new file mode 100644
index 00000000000..0ecbac7b5b5
--- /dev/null
+++ b/docs/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+Allow: /
+Sitemap: https://docs.pyrogram.org/sitemap.xml
\ No newline at end of file
From 7f78a1a504d73ee5d4dd874e36ede0ca33557dbd Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 23 May 2019 08:34:33 +0200
Subject: [PATCH 0159/1652] Add MESSAGE_AUTHOR_REQUIRED error
---
compiler/error/source/403_FORBIDDEN.tsv | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/error/source/403_FORBIDDEN.tsv
index ddd8d26f544..dd1e98fad04 100644
--- a/compiler/error/source/403_FORBIDDEN.tsv
+++ b/compiler/error/source/403_FORBIDDEN.tsv
@@ -3,4 +3,5 @@ CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat
-CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat
\ No newline at end of file
+CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat
+MESSAGE_AUTHOR_REQUIRED You are not the author of this message
\ No newline at end of file
From d34daa9edcb5684e823a816c865ea290389bac38 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 23 May 2019 18:57:59 +0200
Subject: [PATCH 0160/1652] Add pyrogram.png
---
docs/source/_images/pyrogram.png | Bin 0 -> 48928 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 docs/source/_images/pyrogram.png
diff --git a/docs/source/_images/pyrogram.png b/docs/source/_images/pyrogram.png
new file mode 100644
index 0000000000000000000000000000000000000000..caadf9838f058c96fe1b584afa75a398f77f7a8a
GIT binary patch
literal 48928
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ξ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